diff --git a/src/lib/components/atoms/OverlayAvatar.svelte b/src/lib/components/atoms/OverlayAvatar.svelte
new file mode 100644
index 0000000..500a8ba
--- /dev/null
+++ b/src/lib/components/atoms/OverlayAvatar.svelte
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
{$_('overlay.avatar.profile')}
+
+
+
+
+
+
+
+ {$_('overlay.avatar.dark_mode')}
+ Slider missing
+
+
+
+ {$_('overlay.avatar.about_anvil')}
+
+
+
+ {$_('overlay.avatar.sign_out')}
+
+
diff --git a/src/lib/i18n/locales/bg.json b/src/lib/i18n/locales/bg.json
index 0c467a0..943ea24 100644
--- a/src/lib/i18n/locales/bg.json
+++ b/src/lib/i18n/locales/bg.json
@@ -1,4 +1,13 @@
{
+ "overlay": {
+ "avatar": {
+ "about_anvil": "",
+ "dark_mode": "",
+ "profile": "",
+ "settings": "",
+ "sign_out": ""
+ }
+ },
"page": {
"import_project": {
"form": {
diff --git a/src/lib/i18n/locales/de.json b/src/lib/i18n/locales/de.json
index 0f7880f..cf731cc 100644
--- a/src/lib/i18n/locales/de.json
+++ b/src/lib/i18n/locales/de.json
@@ -1,4 +1,13 @@
{
+ "overlay": {
+ "avatar": {
+ "about_anvil": "",
+ "dark_mode": "",
+ "profile": "",
+ "settings": "",
+ "sign_out": ""
+ }
+ },
"page": {
"import_project": {
"form": {
diff --git a/src/lib/i18n/locales/en.json b/src/lib/i18n/locales/en.json
index b78cbb8..c6be5a1 100644
--- a/src/lib/i18n/locales/en.json
+++ b/src/lib/i18n/locales/en.json
@@ -1,4 +1,13 @@
{
+ "overlay": {
+ "avatar": {
+ "about_anvil": "About Anvil",
+ "dark_mode": "Dark mode",
+ "profile": "Profile",
+ "settings": "Settings",
+ "sign_out": "Sign out"
+ }
+ },
"page": {
"import_project": {
"form": {
diff --git a/src/lib/i18n/locales/he.json b/src/lib/i18n/locales/he.json
index d64b747..b72aa4f 100644
--- a/src/lib/i18n/locales/he.json
+++ b/src/lib/i18n/locales/he.json
@@ -1,4 +1,13 @@
{
+ "overlay": {
+ "avatar": {
+ "about_anvil": "",
+ "dark_mode": "",
+ "profile": "",
+ "settings": "",
+ "sign_out": ""
+ }
+ },
"page": {
"import_project": {
"form": {
diff --git a/src/lib/i18n/locales/pl.json b/src/lib/i18n/locales/pl.json
index 48beaac..b36aae5 100644
--- a/src/lib/i18n/locales/pl.json
+++ b/src/lib/i18n/locales/pl.json
@@ -1,4 +1,13 @@
{
+ "overlay": {
+ "avatar": {
+ "about_anvil": "",
+ "dark_mode": "",
+ "profile": "",
+ "settings": "",
+ "sign_out": ""
+ }
+ },
"page": {
"import_project": {
"form": {
diff --git a/stories/atoms/OverlayAvatar.stories.ts b/stories/atoms/OverlayAvatar.stories.ts
new file mode 100644
index 0000000..3c8a91f
--- /dev/null
+++ b/stories/atoms/OverlayAvatar.stories.ts
@@ -0,0 +1,26 @@
+/* Stories for OverlayAvatar atom.
+ * 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 .
+ */
+
+import type { Meta, StoryObj } from '@storybook/svelte';
+
+import OverlayAvatar from '$lib/components/atoms/OverlayAvatar.svelte';
+
+const meta = {
+ title: 'Atoms/OverlayAvatar',
+ component: OverlayAvatar,
+ tags: ['autodocs']
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Plain: Story = {};
diff --git a/tests/components/atoms/OverlayAvatar.test.ts b/tests/components/atoms/OverlayAvatar.test.ts
new file mode 100644
index 0000000..39205ab
--- /dev/null
+++ b/tests/components/atoms/OverlayAvatar.test.ts
@@ -0,0 +1,137 @@
+/* Component test for OverlayAvatar atom.
+ * 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 .
+ */
+
+import { initializeStores } from '@skeletonlabs/skeleton';
+import '@testing-library/jest-dom';
+import { render, screen } from '@testing-library/svelte';
+import { readable } from 'svelte/store';
+import { init, locale, register } from 'svelte-i18n';
+
+import OverlayAvatar from '../../../src/lib/components/atoms/OverlayAvatar.svelte';
+import enMessages from '../../../src/lib/i18n/locales/en.json';
+
+describe('OverlayAvatar.svelte', () => {
+ beforeEach(() => {
+ register('en', () => import('../../../src/lib/i18n/locales/en.json'));
+ init({ fallbackLocale: 'en', initialLocale: 'en' });
+ locale.set('en');
+ });
+
+ it('should mount', () => {
+ // Arrange
+ // See https://testing-library.com/docs/svelte-testing-library/example#contexts
+ const modalStore = readable([]);
+
+ // Act
+ const { container } = render(OverlayAvatar, {
+ context: new Map([['modalStore', modalStore]])
+ });
+
+ // Assert
+ expect(container).toBeTruthy();
+ });
+
+ it('should have a Profile item', () => {
+ // Arrange
+ const modalStore = readable([]);
+
+ // Act
+ render(OverlayAvatar, {
+ context: new Map([['modalStore', modalStore]])
+ });
+
+ // Assert
+ expect(screen.getByText(enMessages.overlay.avatar.profile)).toBeInTheDocument();
+ });
+
+ it('should have a Settings item', () => {
+ // Arrange
+ const modalStore = readable([]);
+
+ // Act
+ render(OverlayAvatar, {
+ context: new Map([['modalStore', modalStore]])
+ });
+
+ // Assert
+ expect(screen.getByText(enMessages.overlay.avatar.settings)).toBeInTheDocument();
+ });
+
+ it('should have a Dark mode item', () => {
+ // Arrange
+ const modalStore = readable([]);
+
+ // Act
+ render(OverlayAvatar, {
+ context: new Map([['modalStore', modalStore]])
+ });
+
+ // Assert
+ expect(screen.getByText(enMessages.overlay.avatar.dark_mode)).toBeInTheDocument();
+ });
+
+ it('should have a About Anvil item', () => {
+ // Arrange
+ const modalStore = readable([]);
+
+ // Act
+ render(OverlayAvatar, {
+ context: new Map([['modalStore', modalStore]])
+ });
+
+ // Assert
+ expect(screen.getByText(enMessages.overlay.avatar.about_anvil)).toBeInTheDocument();
+ });
+
+ it('should have a Sign out item', () => {
+ // Arrange
+ const modalStore = readable([]);
+
+ // Act
+ render(OverlayAvatar, {
+ context: new Map([['modalStore', modalStore]])
+ });
+
+ // Assert
+ expect(screen.getByText(enMessages.overlay.avatar.sign_out)).toBeInTheDocument();
+ });
+
+ it('should open a modal on click on settings', () => {
+ // Arrange
+ const triggerSpy = vi.fn();
+ const { subscribe, set, update } = readable([]);
+
+ // Act
+ render(OverlayAvatar, {
+ context: new Map([
+ [
+ 'modalStore',
+ {
+ subscribe,
+ set,
+ update,
+ trigger: triggerSpy,
+ close: vi.fn(),
+ clear: vi.fn()
+ }
+ ]
+ ])
+ });
+ const settings = screen.getByRole('button', {
+ name: enMessages.overlay.avatar.settings
+ });
+ settings.click();
+
+ // Assert
+ expect(triggerSpy).toHaveBeenCalledOnce();
+ });
+});
diff --git a/tests/components/setup.ts b/tests/components/setup.ts
new file mode 100644
index 0000000..7b091ce
--- /dev/null
+++ b/tests/components/setup.ts
@@ -0,0 +1,13 @@
+Object.defineProperty(window, 'matchMedia', {
+ writable: true,
+ value: vi.fn().mockImplementation((query) => ({
+ matches: false,
+ media: query,
+ onchange: null,
+ addListener: vi.fn(), // deprecated
+ removeListener: vi.fn(), // deprecated
+ addEventListener: vi.fn(),
+ removeEventListener: vi.fn(),
+ dispatchEvent: vi.fn()
+ }))
+});
diff --git a/vitest.config.ts b/vitest.config.ts
index ee3f64d..ac4d023 100644
--- a/vitest.config.ts
+++ b/vitest.config.ts
@@ -13,6 +13,7 @@
import { resolve } from 'node:path';
import { svelte } from '@sveltejs/vite-plugin-svelte';
+import { svelteTesting } from '@testing-library/svelte/vite';
import { defineConfig } from 'vitest/config';
export default defineConfig({
@@ -22,13 +23,14 @@ export default defineConfig({
$lib: resolve('./src/lib')
}
},
- plugins: [svelte({ hot: !process.env.VITEST })],
+ plugins: [svelte({ hot: !process.env.VITEST }), svelteTesting()],
test: {
coverage: {
include: ['src']
},
include: ['tests/**/*.test.ts'],
environment: 'jsdom',
- globals: true
+ globals: true,
+ setupFiles: [resolve(__dirname, 'tests/components/setup.ts')]
}
});