From 179db76b5a004795b6c9997a2231ea6393391451 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Jaenisch?= Date: Mon, 8 Jul 2024 12:17:31 +0200 Subject: [PATCH] refactor: extract OverlayAvatar into component MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I'm going to break up my MainMenu into smaller components, starting with the OverlayAvatar. Testing it taught me how to handle stores in Vitest. Signed-off-by: André Jaenisch --- src/lib/components/atoms/OverlayAvatar.svelte | 53 +++++++ src/lib/i18n/locales/bg.json | 9 ++ src/lib/i18n/locales/de.json | 9 ++ src/lib/i18n/locales/en.json | 9 ++ src/lib/i18n/locales/he.json | 9 ++ src/lib/i18n/locales/pl.json | 9 ++ stories/atoms/OverlayAvatar.stories.ts | 26 ++++ tests/components/atoms/OverlayAvatar.test.ts | 137 ++++++++++++++++++ tests/components/setup.ts | 13 ++ vitest.config.ts | 6 +- 10 files changed, 278 insertions(+), 2 deletions(-) create mode 100644 src/lib/components/atoms/OverlayAvatar.svelte create mode 100644 stories/atoms/OverlayAvatar.stories.ts create mode 100644 tests/components/atoms/OverlayAvatar.test.ts create mode 100644 tests/components/setup.ts 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')] } });