diff --git a/src/lib/components/pages/Login.svelte b/src/lib/components/pages/Login.svelte
new file mode 100644
index 0000000..e21eaac
--- /dev/null
+++ b/src/lib/components/pages/Login.svelte
@@ -0,0 +1,10 @@
+
+
+
diff --git a/src/lib/components/templates/Login.svelte b/src/lib/components/templates/Login.svelte
new file mode 100644
index 0000000..d60b436
--- /dev/null
+++ b/src/lib/components/templates/Login.svelte
@@ -0,0 +1,47 @@
+
+
+
+
+
{$_('page.login.heading')}
+
{$_('page.login.intro')}
+
+
+
diff --git a/src/lib/i18n/locales/de.json b/src/lib/i18n/locales/de.json
index f387107..a8d0451 100644
--- a/src/lib/i18n/locales/de.json
+++ b/src/lib/i18n/locales/de.json
@@ -1,60 +1,83 @@
{
- "page": {
- "import_project": {
- "form": {
- "avatar": "Avatar",
- "components": "Komponenten",
- "fields": {
- "avatar": {
- "label": "Bild hochladen"
- },
- "description": {
- "label": "Beschreibung",
- "placeholder": "Beschreibe das Projekt in unter 100 Zeichen..."
- },
- "issues": {
- "label": "Issues"
- },
- "name": {
- "error": "Dieses Feld ist erforderlich",
- "label": "Name",
- "placeholder": "Name des Projekts..."
- },
- "pr": {
- "label": "Pullrequest"
- },
- "repository": {
- "hint": "optional: von Git-Repository importieren",
- "label": "Repository",
- "placeholder": "URL des Git-Repositorys"
- }
- },
- "submit": "Projekt erstellen"
- },
- "heading": "Projekt erstellen",
- "intro": "Neues F2-Projekt zu Anvil hinzufügen."
- },
- "profile": {
- "activities": {
- "block_or_report": "{blockElementOpen}blockieren{blockElementClose} oder {reportElementOpen}melden{reportElementClose}",
- "like": "Gefällt mir"
- },
- "heading": "Profil für",
- "history": {
- "activities": {
- "setup": {
- "description": "Das F2-Konto @{username}@{instance} wurde erfolgreich innerhalb von {created_with} erstellt",
- "summary": "Kontoeinstellungen"
- }
- },
- "heading": "Aktivitäten"
- },
- "projects": {
- "add_or_import": "{addElementOpen}Ein Projekt hinzufügen{addElementClose} oder {importElementOpen}Ein Projekt importieren{importElementClose}.",
- "empty": "Bisher keine Projekte hinzugefügt.",
- "heading": "Projekte"
- }
- },
- "welcome": "Willkommen bei Anvil!"
- }
+ "page": {
+ "import_project": {
+ "form": {
+ "avatar": "Avatar",
+ "components": "Komponenten",
+ "fields": {
+ "avatar": {
+ "label": "Bild hochladen"
+ },
+ "description": {
+ "label": "Beschreibung",
+ "placeholder": "Beschreibe das Projekt in unter 100 Zeichen..."
+ },
+ "issues": {
+ "label": "Issues"
+ },
+ "name": {
+ "error": "Dieses Feld ist erforderlich",
+ "label": "Name",
+ "placeholder": "Name des Projekts..."
+ },
+ "pr": {
+ "label": "Pullrequest"
+ },
+ "repository": {
+ "hint": "optional: von Git-Repository importieren",
+ "label": "Repository",
+ "placeholder": "URL des Git-Repositorys"
+ }
+ },
+ "submit": "Projekt erstellen"
+ },
+ "heading": "Projekt erstellen",
+ "intro": "Neues F2-Projekt zu Anvil hinzufügen."
+ },
+ "login": {
+ "form": {
+ "validation": {
+ "incorrect": "",
+ "missing": ""
+ },
+ "fields": {
+ "account": {
+ "label": ""
+ },
+ "passphrase": {
+ "label": ""
+ },
+ "server": {
+ "label": ""
+ }
+ },
+ "reset": "",
+ "submit": ""
+ },
+ "heading": "",
+ "intro": ""
+ },
+ "profile": {
+ "activities": {
+ "block_or_report": "{blockElementOpen}blockieren{blockElementClose} oder {reportElementOpen}melden{reportElementClose}",
+ "like": "Gefällt mir"
+ },
+ "heading": "Profil für",
+ "history": {
+ "activities": {
+ "setup": {
+ "description": "Das F2-Konto @{username}@{instance} wurde erfolgreich innerhalb von {created_with} erstellt",
+ "summary": "Kontoeinstellungen"
+ }
+ },
+ "heading": "Aktivitäten"
+ },
+ "projects": {
+ "add_or_import": "{addElementOpen}Ein Projekt hinzufügen{addElementClose} oder {importElementOpen}Ein Projekt importieren{importElementClose}.",
+ "empty": "Bisher keine Projekte hinzugefügt.",
+ "heading": "Projekte"
+ }
+ },
+ "welcome": "Willkommen bei Anvil!"
+ }
}
diff --git a/src/lib/i18n/locales/en.json b/src/lib/i18n/locales/en.json
index 2a00138..9477cdc 100644
--- a/src/lib/i18n/locales/en.json
+++ b/src/lib/i18n/locales/en.json
@@ -34,6 +34,29 @@
"heading": "Create project",
"intro": "Add a new F2 project to Anvil."
},
+ "login": {
+ "form": {
+ "validation": {
+ "incorrect": "Account or password wrong.",
+ "missing": "The account field is required."
+ },
+ "fields": {
+ "account": {
+ "label": "Account name"
+ },
+ "passphrase": {
+ "label": "Passphrase"
+ },
+ "server": {
+ "label": "F2 server"
+ }
+ },
+ "reset": "Reset passphrase",
+ "submit": "Log in"
+ },
+ "heading": "Log in",
+ "intro": "To use Anvil with your F2 account, fill in your credentials."
+ },
"profile": {
"activities": {
"block_or_report": "{blockElementOpen}block{blockElementClose} or {reportElementOpen}report{reportElementClose}",
diff --git a/src/lib/i18n/locales/he.json b/src/lib/i18n/locales/he.json
index af7d16e..e7cc773 100644
--- a/src/lib/i18n/locales/he.json
+++ b/src/lib/i18n/locales/he.json
@@ -34,6 +34,29 @@
"heading": "",
"intro": ""
},
+ "login": {
+ "form": {
+ "validation": {
+ "incorrect": "",
+ "missing": ""
+ },
+ "fields": {
+ "account": {
+ "label": ""
+ },
+ "passphrase": {
+ "label": ""
+ },
+ "server": {
+ "label": ""
+ }
+ },
+ "reset": "",
+ "submit": ""
+ },
+ "heading": "",
+ "intro": ""
+ },
"profile": {
"activities": {
"block_or_report": "",
diff --git a/src/routes/account/login/+page.svelte b/src/routes/account/login/+page.svelte
index 082e6aa..f332ffd 100644
--- a/src/routes/account/login/+page.svelte
+++ b/src/routes/account/login/+page.svelte
@@ -1,35 +1,8 @@
-
-
-
Log in
-
To use Anvil with your F2 account, fill in your credentials.
-
-
-
+
diff --git a/tests/components/pages/Login.test.ts b/tests/components/pages/Login.test.ts
new file mode 100644
index 0000000..0604aa4
--- /dev/null
+++ b/tests/components/pages/Login.test.ts
@@ -0,0 +1,168 @@
+import '@testing-library/jest-dom';
+import { render, screen } from '@testing-library/svelte';
+import { init, locale, register } from 'svelte-i18n';
+
+import Login from '../../../src/lib/components/pages/Login.svelte';
+import enMessages from '../../../src/lib/i18n/locales/en.json';
+
+describe('Login.svelte', () => {
+ beforeEach(() => {
+ register('en', () => import('../../../src/lib/i18n/locales/en.json'));
+ init({ fallbackLocale: 'en', initialLocale: 'en' });
+ locale.set('en');
+ });
+
+ it('should mount', () => {
+ // Arrange
+ const form = {};
+
+ // Act
+ const { container } = render(Login, { form });
+
+ // Assert
+ expect(container).toBeTruthy();
+ });
+
+ it('should have a h1', () => {
+ // Arrange
+ const form = {};
+
+ // Act
+ render(Login, { form });
+ const h1 = screen.getByRole('heading', { level: 1 });
+
+ // Assert
+ expect(h1).toBeInTheDocument();
+ expect(h1).toHaveTextContent(enMessages.page.login.heading);
+ });
+
+ it('should have an intro', () => {
+ // Arrange
+ const form = {};
+
+ // Act
+ render(Login, { form });
+ const intro = screen.getByText(enMessages.page.login.intro);
+
+ // Assert
+ expect(intro).toBeInTheDocument();
+ });
+
+ it('should have a form', () => {
+ // Arrange
+ const form = {};
+
+ // Act
+ render(Login, { form });
+ const formElement = screen.getByRole('form');
+
+ // Assert
+ expect(formElement).toBeInTheDocument();
+ });
+
+ it('should have a server select control', () => {
+ // Arrange
+ const form = {};
+
+ // Act
+ render(Login, { form });
+ const server = screen.getByLabelText(enMessages.page.login.form.fields.server.label);
+
+ // Assert
+ expect(server).toBeInTheDocument();
+ });
+
+ it('should have a account input control', () => {
+ // Arrange
+ const form = {};
+
+ // Act
+ render(Login, { form });
+ const account = screen.getByLabelText(enMessages.page.login.form.fields.account.label);
+
+ // Assert
+ expect(account).toBeInTheDocument();
+ });
+
+ it('should have a passphrase input control', () => {
+ // Arrange
+ const form = {};
+
+ // Act
+ render(Login, { form });
+ const passphrase = screen.getByLabelText(enMessages.page.login.form.fields.passphrase.label);
+
+ // Assert
+ expect(passphrase).toBeInTheDocument();
+ });
+
+ it('should have a reset passphrase link', () => {
+ // Arrange
+ const form = {};
+
+ // Act
+ render(Login, { form });
+ const reset = screen.getByRole('link', { name: enMessages.page.login.form.reset });
+
+ // Assert
+ expect(reset).toBeInTheDocument();
+ });
+
+ it('should have a submit button', () => {
+ // Arrange
+ const form = {};
+
+ // Act
+ render(Login, { form });
+ const submit = screen.getByRole('button', { name: enMessages.page.login.form.submit });
+
+ // Assert
+ expect(submit).toBeInTheDocument();
+ });
+
+ it('should have no validation errors', () => {
+ // Arrange
+ const form = {};
+
+ // Act
+ render(Login, { form });
+
+ // Assert
+ expect(
+ screen.queryByText(enMessages.page.login.form.validation.incorrect)
+ ).not.toBeInTheDocument();
+ expect(
+ screen.queryByText(enMessages.page.login.form.validation.missing)
+ ).not.toBeInTheDocument();
+ });
+
+ describe('when validation failed', () => {
+ it('should show an error on required inputs', () => {
+ // Arrange
+ const form = {
+ missing: true
+ };
+
+ // Act
+ render(Login, { form });
+ const intro = screen.getByText(enMessages.page.login.form.validation.missing);
+
+ // Assert
+ expect(intro).toBeInTheDocument();
+ });
+
+ it('should show an error on invalid inputs', () => {
+ // Arrange
+ const form = {
+ incorrect: true
+ };
+
+ // Act
+ render(Login, { form });
+ const intro = screen.getByText(enMessages.page.login.form.validation.incorrect);
+
+ // Assert
+ expect(intro).toBeInTheDocument();
+ });
+ });
+});
diff --git a/tests/components/templates/Login.test.ts b/tests/components/templates/Login.test.ts
new file mode 100644
index 0000000..d79cde6
--- /dev/null
+++ b/tests/components/templates/Login.test.ts
@@ -0,0 +1,168 @@
+import '@testing-library/jest-dom';
+import { render, screen } from '@testing-library/svelte';
+import { init, locale, register } from 'svelte-i18n';
+
+import Login from '../../../src/lib/components/templates/Login.svelte';
+import enMessages from '../../../src/lib/i18n/locales/en.json';
+
+describe('Login.svelte', () => {
+ beforeEach(() => {
+ register('en', () => import('../../../src/lib/i18n/locales/en.json'));
+ init({ fallbackLocale: 'en', initialLocale: 'en' });
+ locale.set('en');
+ });
+
+ it('should mount', () => {
+ // Arrange
+ const form = {};
+
+ // Act
+ const { container } = render(Login, { form });
+
+ // Assert
+ expect(container).toBeTruthy();
+ });
+
+ it('should have a h1', () => {
+ // Arrange
+ const form = {};
+
+ // Act
+ render(Login, { form });
+ const h1 = screen.getByRole('heading', { level: 1 });
+
+ // Assert
+ expect(h1).toBeInTheDocument();
+ expect(h1).toHaveTextContent(enMessages.page.login.heading);
+ });
+
+ it('should have an intro', () => {
+ // Arrange
+ const form = {};
+
+ // Act
+ render(Login, { form });
+ const intro = screen.getByText(enMessages.page.login.intro);
+
+ // Assert
+ expect(intro).toBeInTheDocument();
+ });
+
+ it('should have a form', () => {
+ // Arrange
+ const form = {};
+
+ // Act
+ render(Login, { form });
+ const formElement = screen.getByRole('form');
+
+ // Assert
+ expect(formElement).toBeInTheDocument();
+ });
+
+ it('should have a server select control', () => {
+ // Arrange
+ const form = {};
+
+ // Act
+ render(Login, { form });
+ const server = screen.getByLabelText(enMessages.page.login.form.fields.server.label);
+
+ // Assert
+ expect(server).toBeInTheDocument();
+ });
+
+ it('should have a account input control', () => {
+ // Arrange
+ const form = {};
+
+ // Act
+ render(Login, { form });
+ const account = screen.getByLabelText(enMessages.page.login.form.fields.account.label);
+
+ // Assert
+ expect(account).toBeInTheDocument();
+ });
+
+ it('should have a passphrase input control', () => {
+ // Arrange
+ const form = {};
+
+ // Act
+ render(Login, { form });
+ const passphrase = screen.getByLabelText(enMessages.page.login.form.fields.passphrase.label);
+
+ // Assert
+ expect(passphrase).toBeInTheDocument();
+ });
+
+ it('should have a reset passphrase link', () => {
+ // Arrange
+ const form = {};
+
+ // Act
+ render(Login, { form });
+ const reset = screen.getByRole('link', { name: enMessages.page.login.form.reset });
+
+ // Assert
+ expect(reset).toBeInTheDocument();
+ });
+
+ it('should have a submit button', () => {
+ // Arrange
+ const form = {};
+
+ // Act
+ render(Login, { form });
+ const submit = screen.getByRole('button', { name: enMessages.page.login.form.submit });
+
+ // Assert
+ expect(submit).toBeInTheDocument();
+ });
+
+ it('should have no validation errors', () => {
+ // Arrange
+ const form = {};
+
+ // Act
+ render(Login, { form });
+
+ // Assert
+ expect(
+ screen.queryByText(enMessages.page.login.form.validation.incorrect)
+ ).not.toBeInTheDocument();
+ expect(
+ screen.queryByText(enMessages.page.login.form.validation.missing)
+ ).not.toBeInTheDocument();
+ });
+
+ describe('when validation failed', () => {
+ it('should show an error on required inputs', () => {
+ // Arrange
+ const form = {
+ missing: true
+ };
+
+ // Act
+ render(Login, { form });
+ const intro = screen.getByText(enMessages.page.login.form.validation.missing);
+
+ // Assert
+ expect(intro).toBeInTheDocument();
+ });
+
+ it('should show an error on invalid inputs', () => {
+ // Arrange
+ const form = {
+ incorrect: true
+ };
+
+ // Act
+ render(Login, { form });
+ const intro = screen.getByText(enMessages.page.login.form.validation.incorrect);
+
+ // Assert
+ expect(intro).toBeInTheDocument();
+ });
+ });
+});