From 7484bfeb1aa7105e60eb596c9737053a27265bea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Jaenisch?= Date: Sat, 6 Jul 2024 18:29:39 +0200 Subject: [PATCH] refactor: extract Welcome page into component MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows for proper testing and documentation in Storybook. I also moved the image into its own component. This required me to adjust the vitest.config.ts slightly because it wasn't finding the image during tests. Since components are fully translatable, this means we have new keys now. Signed-off-by: André Jaenisch --- package-lock.json | 4 +- package.json | 2 +- src/lib/components/atoms/AnvilLogo.svelte | 11 +- src/lib/components/molecules/Welcome.svelte | 48 +++++++-- src/lib/components/pages/Welcome.svelte | 18 ++++ src/lib/components/templates/Welcome.svelte | 32 ++++++ src/lib/i18n/locales/bg.json | 8 +- src/lib/i18n/locales/de.json | 8 +- src/lib/i18n/locales/en.json | 8 +- src/lib/i18n/locales/he.json | 8 +- src/lib/i18n/locales/pl.json | 8 +- src/routes/+page.svelte | 2 +- stories/molecules/Welcome.stories.ts | 27 ++++- stories/pages/Welcome.stories.ts | 26 +++++ stories/templates/Welcome.stories.ts | 26 +++++ tests/components/atoms/AnvilLogo.test.ts | 74 +++++++++++++ tests/components/molecules/Welcome.test.ts | 73 +++++++++++++ tests/components/pages/Welcome.test.ts | 109 ++++++++++++++++++++ tests/components/templates/Welcome.test.ts | 109 ++++++++++++++++++++ vitest.config.ts | 10 +- 20 files changed, 591 insertions(+), 20 deletions(-) create mode 100644 src/lib/components/pages/Welcome.svelte create mode 100644 src/lib/components/templates/Welcome.svelte create mode 100644 stories/pages/Welcome.stories.ts create mode 100644 stories/templates/Welcome.stories.ts create mode 100644 tests/components/atoms/AnvilLogo.test.ts create mode 100644 tests/components/molecules/Welcome.test.ts create mode 100644 tests/components/pages/Welcome.test.ts create mode 100644 tests/components/templates/Welcome.test.ts diff --git a/package-lock.json b/package-lock.json index fe44f24..07f7f32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "anvil", - "version": "0.0.6", + "version": "0.0.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "anvil", - "version": "0.0.6", + "version": "0.0.7", "dependencies": { "axios": "1.7.2", "cheerio": "1.0.0-rc.12" diff --git a/package.json b/package.json index ec313f4..623953d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "anvil", - "version": "0.0.6", + "version": "0.0.7", "private": true, "scripts": { "dev": "vite dev", diff --git a/src/lib/components/atoms/AnvilLogo.svelte b/src/lib/components/atoms/AnvilLogo.svelte index 05e385a..d801361 100644 --- a/src/lib/components/atoms/AnvilLogo.svelte +++ b/src/lib/components/atoms/AnvilLogo.svelte @@ -12,6 +12,8 @@ You should have received a copy of the GNU Affero General Public License along w --> -Anvil logo +{$_(i18n.alt)} diff --git a/src/lib/components/molecules/Welcome.svelte b/src/lib/components/molecules/Welcome.svelte index 682d109..1dd4e81 100644 --- a/src/lib/components/molecules/Welcome.svelte +++ b/src/lib/components/molecules/Welcome.svelte @@ -15,27 +15,61 @@ You should have received a copy of the GNU Affero General Public License along w import { _ } from 'svelte-i18n'; import AnvilLogo from '../atoms/AnvilLogo.svelte'; + + /** + * Translation keys. + */ + export let i18n = { + create: '', + headline: '', + intro: '', + login: '', + logo: { + alt: '' + }, + reset: '' + }; + + /** + * Allow for linking stories in Storybook. + */ + export let sbCreate = ''; + + /** + * Allow for linking stories in Storybook. + */ + export let sbLogin = ''; + + /** + * Allow for linking stories in Storybook. + */ + export let sbReset = '';
- -

{$_('page.welcome.headline')}

-

{$_('page.welcome.intro')}

+ +

{$_(i18n.headline)}

+

{$_(i18n.intro)}

diff --git a/src/lib/components/pages/Welcome.svelte b/src/lib/components/pages/Welcome.svelte new file mode 100644 index 0000000..0531998 --- /dev/null +++ b/src/lib/components/pages/Welcome.svelte @@ -0,0 +1,18 @@ + + + + + diff --git a/src/lib/components/templates/Welcome.svelte b/src/lib/components/templates/Welcome.svelte new file mode 100644 index 0000000..245b8a4 --- /dev/null +++ b/src/lib/components/templates/Welcome.svelte @@ -0,0 +1,32 @@ + + + + + diff --git a/src/lib/i18n/locales/bg.json b/src/lib/i18n/locales/bg.json index 57fb854..9267dc3 100644 --- a/src/lib/i18n/locales/bg.json +++ b/src/lib/i18n/locales/bg.json @@ -128,8 +128,14 @@ } }, "welcome": { + "create": "", + "intro": "", "headline": "", - "intro": "" + "login": "", + "logo": { + "alt": "" + }, + "reset": "" } } } diff --git a/src/lib/i18n/locales/de.json b/src/lib/i18n/locales/de.json index c63ee57..0f7880f 100644 --- a/src/lib/i18n/locales/de.json +++ b/src/lib/i18n/locales/de.json @@ -128,8 +128,14 @@ } }, "welcome": { + "create": "", + "intro": "", "headline": "Willkommen bei Anvil", - "intro": "" + "login": "", + "logo": { + "alt": "" + }, + "reset": "" } } } diff --git a/src/lib/i18n/locales/en.json b/src/lib/i18n/locales/en.json index 4ca1a5f..b78cbb8 100644 --- a/src/lib/i18n/locales/en.json +++ b/src/lib/i18n/locales/en.json @@ -128,8 +128,14 @@ } }, "welcome": { + "create": "Create F2 account", "headline": "Welcome to Anvil", - "intro": "Anvil is a F2 („ForgeFed”) client. If you don't have an F2 account you need to create one on an F2 server." + "intro": "Anvil is a F2 („ForgeFed”) client. If you don't have an F2 account you need to create one on an F2 server.", + "login": "I have an account", + "logo": { + "alt": "Anvil logo" + }, + "reset": "Reset passphrase" } } } diff --git a/src/lib/i18n/locales/he.json b/src/lib/i18n/locales/he.json index a01b511..d64b747 100644 --- a/src/lib/i18n/locales/he.json +++ b/src/lib/i18n/locales/he.json @@ -128,8 +128,14 @@ } }, "welcome": { + "create": "", "headline": "", - "intro": "" + "intro": "", + "login": "", + "logo": { + "alt": "" + }, + "reset": "" } } } diff --git a/src/lib/i18n/locales/pl.json b/src/lib/i18n/locales/pl.json index 81ff289..48beaac 100644 --- a/src/lib/i18n/locales/pl.json +++ b/src/lib/i18n/locales/pl.json @@ -128,8 +128,14 @@ } }, "welcome": { + "create": "", "headline": "Witamy w Anvil", - "intro": "" + "intro": "", + "login": "", + "logo": { + "alt": "" + }, + "reset": "" } } } diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 28d7511..192a4e6 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/stories/molecules/Welcome.stories.ts b/stories/molecules/Welcome.stories.ts index 692fc6c..74b07ca 100644 --- a/stories/molecules/Welcome.stories.ts +++ b/stories/molecules/Welcome.stories.ts @@ -17,10 +17,33 @@ import Welcome from '$lib/components/molecules/Welcome.svelte'; const meta = { title: 'Molecules/Welcome', component: Welcome, - tags: ['autodocs'] + tags: ['autodocs'], + args: { + sbCreate: 'Templates/Login', + sbLogin: 'Templates/Login', + sbReset: 'Templates/Login' + }, + argTypes: { + sbCreate: { control: 'radio', options: ['Templates/Login'] }, + sbLogin: { control: 'radio', options: ['Templates/Login'] }, + sbReset: { control: 'radio', options: ['Templates/Login'] } + } } satisfies Meta; export default meta; type Story = StoryObj; -export const Plain: Story = {}; +export const Plain: Story = { + args: { + i18n: { + create: 'page.welcome.create', + headline: 'page.welcome.headline', + intro: 'page.welcome.intro', + login: 'page.welcome.login', + logo: { + alt: 'page.welcome.logo.alt' + }, + reset: 'page.welcome.reset' + } + } +}; diff --git a/stories/pages/Welcome.stories.ts b/stories/pages/Welcome.stories.ts new file mode 100644 index 0000000..560cdc9 --- /dev/null +++ b/stories/pages/Welcome.stories.ts @@ -0,0 +1,26 @@ +/* Stories for Welcome page. + * 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 Welcome from '$lib/components/pages/Welcome.svelte'; + +const meta = { + title: 'Pages/Welcome', + component: Welcome, + tags: ['autodocs'] +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Plain: Story = {}; diff --git a/stories/templates/Welcome.stories.ts b/stories/templates/Welcome.stories.ts new file mode 100644 index 0000000..031bf0e --- /dev/null +++ b/stories/templates/Welcome.stories.ts @@ -0,0 +1,26 @@ +/* Stories for Welcome template. + * 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 Welcome from '$lib/components/templates/Welcome.svelte'; + +const meta = { + title: 'Templates/Welcome', + component: Welcome, + tags: ['autodocs'] +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Plain: Story = {}; diff --git a/tests/components/atoms/AnvilLogo.test.ts b/tests/components/atoms/AnvilLogo.test.ts new file mode 100644 index 0000000..4f092c4 --- /dev/null +++ b/tests/components/atoms/AnvilLogo.test.ts @@ -0,0 +1,74 @@ +/* Component test for AnvilLogo 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 '@testing-library/jest-dom'; +import { render, screen } from '@testing-library/svelte'; +import { init, locale, register } from 'svelte-i18n'; + +import AnvilLogo from '../../../src/lib/components/atoms/AnvilLogo.svelte'; +import enMessages from '../../../src/lib/i18n/locales/en.json'; + +describe('AnvilLogo.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(AnvilLogo); + + // Assert + expect(container).toBeTruthy(); + }); + + it('should have a logo', () => { + // Arrange + // Nothing to prepare + + // Act + render(AnvilLogo); + + // Assert + // No alt text and therefore no accessible name. Turns role img into presentation + expect(screen.getByRole('presentation')).toBeInTheDocument(); + }); + + it('should have a logo', () => { + // Arrange + const i18n = { + alt: 'Testing Anvil' + }; + + // Act + render(AnvilLogo, { i18n }); + + // Assert + expect(screen.getByRole('img')).toBeInTheDocument(); + }); + + it('should accept arbitrary classes', () => { + // Arrange + const klass = 'w-full'; + + // Act + render(AnvilLogo, { class: klass }); + + // Assert + expect(screen.getByRole('presentation')).toBeInTheDocument(); + expect(screen.getByRole('presentation')).toHaveClass('w-full'); + }); +}); diff --git a/tests/components/molecules/Welcome.test.ts b/tests/components/molecules/Welcome.test.ts new file mode 100644 index 0000000..a04b7d0 --- /dev/null +++ b/tests/components/molecules/Welcome.test.ts @@ -0,0 +1,73 @@ +/* Component test for Welcome 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 . + */ + +import '@testing-library/jest-dom'; +import { render, screen } from '@testing-library/svelte'; +import { init, locale, register } from 'svelte-i18n'; + +import Welcome from '../../../src/lib/components/molecules/Welcome.svelte'; +import enMessages from '../../../src/lib/i18n/locales/en.json'; + +describe('Welcome.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(Welcome); + + // Assert + expect(container).toBeTruthy(); + }); + + it('should have a logo', () => { + // Arrange + // Nothing to prepare + + // Act + render(Welcome); + + // Assert + // No alt text and therefore no accessible name. Turns role img into presentation + expect(screen.getByRole('presentation')).toBeInTheDocument(); + }); + + it('should have a h1', () => { + // Arrange + // Nothing to prepare + + // Act + render(Welcome); + const h1 = screen.getByRole('heading', { level: 1 }); + + // Assert + expect(h1).toBeInTheDocument(); + }); + + it('should have three links', () => { + // Arrange + // Nothing to prepare + + // Act + render(Welcome); + const buttons = screen.getAllByRole('link'); + + // Assert + expect(buttons).toHaveLength(3); + }); +}); diff --git a/tests/components/pages/Welcome.test.ts b/tests/components/pages/Welcome.test.ts new file mode 100644 index 0000000..58d516f --- /dev/null +++ b/tests/components/pages/Welcome.test.ts @@ -0,0 +1,109 @@ +/* Component test for Welcome page. + * 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 '@testing-library/jest-dom'; +import { render, screen } from '@testing-library/svelte'; +import { init, locale, register } from 'svelte-i18n'; + +import Welcome from '../../../src/lib/components/pages/Welcome.svelte'; +import enMessages from '../../../src/lib/i18n/locales/en.json'; + +describe('Welcome.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(Welcome); + + // Assert + expect(container).toBeTruthy(); + }); + + it('should have a logo', () => { + // Arrange + // Nothing to prepare + + // Act + render(Welcome); + + // Assert + expect(screen.getByRole('img')).toBeInTheDocument(); + expect(screen.getByRole('img')).toHaveAccessibleName(enMessages.page.welcome.logo.alt); + }); + + it('should have a h1', () => { + // Arrange + // Nothing to prepare + + // Act + render(Welcome); + const h1 = screen.getByRole('heading', { level: 1 }); + + // Assert + expect(h1).toBeInTheDocument(); + expect(h1).toHaveTextContent(enMessages.page.welcome.headline); + }); + + it('should have an intro text', () => { + // Arrange + // Nothing to prepare + + // Act + render(Welcome); + + // Assert + expect(screen.getByText(enMessages.page.welcome.intro)).toBeInTheDocument(); + }); + + it('should have a signup link', () => { + // Arrange + // Nothing to prepare + + // Act + render(Welcome); + const button = screen.getByRole('link', { name: enMessages.page.welcome.create }); + + // Assert + expect(button).toBeInTheDocument(); + }); + + it('should have a signin link', () => { + // Arrange + // Nothing to prepare + + // Act + render(Welcome); + const button = screen.getByRole('link', { name: enMessages.page.welcome.login }); + + // Assert + expect(button).toBeInTheDocument(); + }); + + it('should have a reset link', () => { + // Arrange + // Nothing to prepare + + // Act + render(Welcome); + const button = screen.getByRole('link', { name: enMessages.page.welcome.reset }); + + // Assert + expect(button).toBeInTheDocument(); + }); +}); diff --git a/tests/components/templates/Welcome.test.ts b/tests/components/templates/Welcome.test.ts new file mode 100644 index 0000000..faf01af --- /dev/null +++ b/tests/components/templates/Welcome.test.ts @@ -0,0 +1,109 @@ +/* Component test for Welcome template. + * 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 '@testing-library/jest-dom'; +import { render, screen } from '@testing-library/svelte'; +import { init, locale, register } from 'svelte-i18n'; + +import Welcome from '../../../src/lib/components/templates/Welcome.svelte'; +import enMessages from '../../../src/lib/i18n/locales/en.json'; + +describe('Welcome.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(Welcome); + + // Assert + expect(container).toBeTruthy(); + }); + + it('should have a logo', () => { + // Arrange + // Nothing to prepare + + // Act + render(Welcome); + + // Assert + expect(screen.getByRole('img')).toBeInTheDocument(); + expect(screen.getByRole('img')).toHaveAccessibleName(enMessages.page.welcome.logo.alt); + }); + + it('should have a h1', () => { + // Arrange + // Nothing to prepare + + // Act + render(Welcome); + const h1 = screen.getByRole('heading', { level: 1 }); + + // Assert + expect(h1).toBeInTheDocument(); + expect(h1).toHaveTextContent(enMessages.page.welcome.headline); + }); + + it('should have an intro text', () => { + // Arrange + // Nothing to prepare + + // Act + render(Welcome); + + // Assert + expect(screen.getByText(enMessages.page.welcome.intro)).toBeInTheDocument(); + }); + + it('should have a signup link', () => { + // Arrange + // Nothing to prepare + + // Act + render(Welcome); + const button = screen.getByRole('link', { name: enMessages.page.welcome.create }); + + // Assert + expect(button).toBeInTheDocument(); + }); + + it('should have a signin link', () => { + // Arrange + // Nothing to prepare + + // Act + render(Welcome); + const button = screen.getByRole('link', { name: enMessages.page.welcome.login }); + + // Assert + expect(button).toBeInTheDocument(); + }); + + it('should have a reset link', () => { + // Arrange + // Nothing to prepare + + // Act + render(Welcome); + const button = screen.getByRole('link', { name: enMessages.page.welcome.reset }); + + // Assert + expect(button).toBeInTheDocument(); + }); +}); diff --git a/vitest.config.ts b/vitest.config.ts index 1bd5d0b..ee3f64d 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -10,14 +10,22 @@ * You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ +import { resolve } from 'node:path'; + import { svelte } from '@sveltejs/vite-plugin-svelte'; import { defineConfig } from 'vitest/config'; export default defineConfig({ + resolve: { + alias: { + // Help vitest find imported images + $lib: resolve('./src/lib') + } + }, plugins: [svelte({ hot: !process.env.VITEST })], test: { coverage: { - include: ['src/**/*.svelte'] + include: ['src'] }, include: ['tests/**/*.test.ts'], environment: 'jsdom',