From 34be485a498a1ab88051f234da9fc4298ab699ac Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Jaenisch?=
Date: Fri, 16 Feb 2024 15:46:32 +0100
Subject: [PATCH] feat: import projects form
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: André Jaenisch
---
package-lock.json | 4 +-
package.json | 2 +-
src/app.html | 2 +-
src/lib/components/pages/ImportProject.svelte | 10 +
.../components/templates/ImportProject.svelte | 119 +++++++
src/lib/i18n/locales/en.json | 34 ++
src/lib/i18n/locales/he.json | 6 +-
src/routes/profile/+page.svelte | 2 +-
src/routes/projects/import/+page.server.js | 4 +
src/routes/projects/import/+page.svelte | 8 +
tests/components/pages/ImportProject.test.ts | 304 ++++++++++++++++++
.../templates/ImportProject.test.ts | 304 ++++++++++++++++++
12 files changed, 791 insertions(+), 8 deletions(-)
create mode 100644 src/lib/components/pages/ImportProject.svelte
create mode 100644 src/lib/components/templates/ImportProject.svelte
create mode 100644 src/routes/projects/import/+page.server.js
create mode 100644 src/routes/projects/import/+page.svelte
create mode 100644 tests/components/pages/ImportProject.test.ts
create mode 100644 tests/components/templates/ImportProject.test.ts
diff --git a/package-lock.json b/package-lock.json
index 1f083f8..bdb8d51 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "anvil",
- "version": "0.0.2",
+ "version": "0.0.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "anvil",
- "version": "0.0.2",
+ "version": "0.0.3",
"devDependencies": {
"@playwright/test": "1.41.2",
"@skeletonlabs/skeleton": "2.8.0",
diff --git a/package.json b/package.json
index 655d738..9304f72 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "anvil",
- "version": "0.0.2",
+ "version": "0.0.3",
"private": true,
"scripts": {
"dev": "vite dev",
diff --git a/src/app.html b/src/app.html
index 5d04ca1..482b79b 100644
--- a/src/app.html
+++ b/src/app.html
@@ -1,5 +1,5 @@
-
+
diff --git a/src/lib/components/pages/ImportProject.svelte b/src/lib/components/pages/ImportProject.svelte
new file mode 100644
index 0000000..b57ae60
--- /dev/null
+++ b/src/lib/components/pages/ImportProject.svelte
@@ -0,0 +1,10 @@
+
+
+
diff --git a/src/lib/components/templates/ImportProject.svelte b/src/lib/components/templates/ImportProject.svelte
new file mode 100644
index 0000000..eb9c212
--- /dev/null
+++ b/src/lib/components/templates/ImportProject.svelte
@@ -0,0 +1,119 @@
+
+
+
diff --git a/src/lib/i18n/locales/en.json b/src/lib/i18n/locales/en.json
index 7a3e437..ce2ce79 100644
--- a/src/lib/i18n/locales/en.json
+++ b/src/lib/i18n/locales/en.json
@@ -1,5 +1,39 @@
{
"page": {
+ "import_project": {
+ "form": {
+ "avatar": "Avatar",
+ "components": "Components",
+ "fields": {
+ "avatar": {
+ "label": "Upload image"
+ },
+ "description": {
+ "label": "Description",
+ "placeholder": "Describe the project in less than 100 characters…"
+ },
+ "issues": {
+ "label": "Issues"
+ },
+ "name": {
+ "error": "This field is required",
+ "label": "Name",
+ "placeholder": "Name of the project…"
+ },
+ "pr": {
+ "label": "Pull Requests"
+ },
+ "repository": {
+ "hint": "optional: import from git repo",
+ "label": "Repository",
+ "placeholder": "url of git repository…"
+ }
+ },
+ "submit": "Create project"
+ },
+ "heading": "Create project",
+ "intro": "Add a new F2 project to Anvil."
+ },
"welcome": "Welcome to Anvil!"
}
}
diff --git a/src/lib/i18n/locales/he.json b/src/lib/i18n/locales/he.json
index 3751aa9..318f15b 100644
--- a/src/lib/i18n/locales/he.json
+++ b/src/lib/i18n/locales/he.json
@@ -1,5 +1,5 @@
{
- "page": {
- "welcome": ""
- }
+ "page": {
+ "welcome": ""
+ }
}
diff --git a/src/routes/profile/+page.svelte b/src/routes/profile/+page.svelte
index 7ee499a..8b44570 100644
--- a/src/routes/profile/+page.svelte
+++ b/src/routes/profile/+page.svelte
@@ -47,7 +47,7 @@
No projects added yet.
Add a project
or
- import a project.
+ import a project.
diff --git a/src/routes/projects/import/+page.server.js b/src/routes/projects/import/+page.server.js
new file mode 100644
index 0000000..e107ab9
--- /dev/null
+++ b/src/routes/projects/import/+page.server.js
@@ -0,0 +1,4 @@
+/** @type {import('./$types').PageServerLoad} */
+export async function load({ params }) {
+ return {};
+}
diff --git a/src/routes/projects/import/+page.svelte b/src/routes/projects/import/+page.svelte
new file mode 100644
index 0000000..c7ad6c1
--- /dev/null
+++ b/src/routes/projects/import/+page.svelte
@@ -0,0 +1,8 @@
+
+
+
diff --git a/tests/components/pages/ImportProject.test.ts b/tests/components/pages/ImportProject.test.ts
new file mode 100644
index 0000000..e84e678
--- /dev/null
+++ b/tests/components/pages/ImportProject.test.ts
@@ -0,0 +1,304 @@
+import '@testing-library/jest-dom';
+import { render, screen } from '@testing-library/svelte';
+import { init, locale, register } from 'svelte-i18n';
+
+import ImportProject from '../../../src/lib/components/templates/ImportProject.svelte';
+import enMessages from '../../../src/lib/i18n/locales/en.json';
+
+describe('ImportProject.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(ImportProject);
+
+ // Assert
+ expect(container).toBeTruthy();
+ });
+
+ it('should have a form', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(ImportProject);
+
+ // Assert
+ expect(screen.getByRole('form')).toBeInTheDocument();
+ });
+
+ it('should have a h2', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(ImportProject);
+ const h2 = screen.getByRole('heading', { level: 2 });
+
+ // Assert
+ expect(h2).toBeInTheDocument();
+ expect(h2).toHaveTextContent(enMessages.page.import_project.heading);
+ });
+
+ it('should have an intro text', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(ImportProject);
+
+ // Assert
+ expect(screen.getByText(enMessages.page.import_project.intro)).toBeInTheDocument();
+ });
+
+ describe('name', () => {
+ it('should have a label', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(ImportProject);
+
+ // Turn into regular expression to account for the asterisk
+ const name = screen.getByLabelText(
+ new RegExp(enMessages.page.import_project.form.fields.name.label)
+ );
+
+ // Assert
+ expect(name).toBeInTheDocument();
+ });
+
+ it('should have a text input', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(ImportProject);
+
+ const name = screen.getByPlaceholderText(
+ enMessages.page.import_project.form.fields.name.placeholder
+ );
+
+ // Assert
+ expect(name).toBeInTheDocument();
+ expect(name).toHaveAttribute('type', 'text');
+ });
+
+ describe('when empty', () => {
+ it('should display an error', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(ImportProject);
+
+ // Assert
+ expect(
+ screen.getByText(enMessages.page.import_project.form.fields.name.error)
+ ).toBeInTheDocument();
+ });
+ });
+
+ // FIXME: Enable once the dev server is running
+ describe.skip('when filled', () => {
+ it('should hide the error', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(ImportProject);
+
+ // Assert
+ expect(
+ screen.getByText(enMessages.page.import_project.form.fields.name.error)
+ ).not.toBeInTheDocument();
+ });
+
+ it('should show a live updating hint', () => {
+ // Arrange
+ const value = 'Vervis';
+
+ // Act
+ render(ImportProject, { value });
+
+ // Assert
+ expect(screen.getByText(`domain.example/projects/${value}`)).toBeInTheDocument();
+ });
+ });
+ });
+
+ describe('description', () => {
+ it('should have a label', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(ImportProject);
+
+ const description = screen.getByLabelText(
+ enMessages.page.import_project.form.fields.description.label
+ );
+
+ // Assert
+ expect(description).toBeInTheDocument();
+ });
+
+ it('should have a textarea', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(ImportProject);
+
+ const description = screen.getByPlaceholderText(
+ enMessages.page.import_project.form.fields.description.placeholder
+ );
+
+ // Assert
+ expect(description).toBeInTheDocument();
+ });
+ });
+
+ describe('avatar', () => {
+ it('should have a heading', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(ImportProject);
+
+ const avatar = screen.getByRole('heading', {
+ level: 3,
+ name: enMessages.page.import_project.form.avatar
+ });
+
+ // Assert
+ expect(avatar).toBeInTheDocument();
+ });
+
+ it('should have an upload button', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(ImportProject);
+
+ // Turn into regular expression to account for the icon
+ const avatar = screen.getByLabelText(
+ new RegExp(enMessages.page.import_project.form.fields.avatar.label)
+ );
+
+ // Assert
+ expect(avatar).toBeInTheDocument();
+ });
+ });
+
+ describe('components', () => {
+ it('should have a heading', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(ImportProject);
+
+ const components = screen.getByRole('heading', {
+ level: 3,
+ name: enMessages.page.import_project.form.components
+ });
+
+ // Assert
+ expect(components).toBeInTheDocument();
+ });
+
+ describe('repository', () => {
+ it('should have a label', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(ImportProject);
+
+ // Turn into regular expression to account for the icon
+ const repository = screen.getByLabelText(
+ enMessages.page.import_project.form.fields.repository.label
+ );
+
+ // Assert
+ expect(repository).toBeInTheDocument();
+ });
+
+ it('should have an URL input', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(ImportProject);
+
+ // Turn into regular expression to account for the icon
+ const repository = screen.getByPlaceholderText(
+ enMessages.page.import_project.form.fields.repository.placeholder
+ );
+
+ // Assert
+ expect(repository).toBeInTheDocument();
+ });
+
+ it('should have a hint', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(ImportProject);
+
+ // Turn into regular expression to account for the icon
+ const repository = screen.getByText(
+ enMessages.page.import_project.form.fields.repository.hint
+ );
+
+ // Assert
+ expect(repository).toBeInTheDocument();
+ });
+ });
+
+ describe('issues', () => {
+ it('should have a label', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(ImportProject);
+
+ // Turn into regular expression to account for the icon
+ const issues = screen.getByLabelText(
+ enMessages.page.import_project.form.fields.issues.label
+ );
+
+ // Assert
+ expect(issues).toBeInTheDocument();
+ });
+ });
+
+ describe('pull requests', () => {
+ it('should have a label', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(ImportProject);
+
+ // Turn into regular expression to account for the icon
+ const issues = screen.getByLabelText(enMessages.page.import_project.form.fields.pr.label);
+
+ // Assert
+ expect(issues).toBeInTheDocument();
+ });
+ });
+ });
+});
diff --git a/tests/components/templates/ImportProject.test.ts b/tests/components/templates/ImportProject.test.ts
new file mode 100644
index 0000000..720e9f9
--- /dev/null
+++ b/tests/components/templates/ImportProject.test.ts
@@ -0,0 +1,304 @@
+import '@testing-library/jest-dom';
+import { render, screen } from '@testing-library/svelte';
+import { init, locale, register } from 'svelte-i18n';
+
+import ImportProject from '../../../src/lib/components/pages/ImportProject.svelte';
+import enMessages from '../../../src/lib/i18n/locales/en.json';
+
+describe('ImportProject.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(ImportProject);
+
+ // Assert
+ expect(container).toBeTruthy();
+ });
+
+ it('should have a form', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(ImportProject);
+
+ // Assert
+ expect(screen.getByRole('form')).toBeInTheDocument();
+ });
+
+ it('should have a h2', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(ImportProject);
+ const h2 = screen.getByRole('heading', { level: 2 });
+
+ // Assert
+ expect(h2).toBeInTheDocument();
+ expect(h2).toHaveTextContent(enMessages.page.import_project.heading);
+ });
+
+ it('should have an intro text', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(ImportProject);
+
+ // Assert
+ expect(screen.getByText(enMessages.page.import_project.intro)).toBeInTheDocument();
+ });
+
+ describe('name', () => {
+ it('should have a label', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(ImportProject);
+
+ // Turn into regular expression to account for the asterisk
+ const name = screen.getByLabelText(
+ new RegExp(enMessages.page.import_project.form.fields.name.label)
+ );
+
+ // Assert
+ expect(name).toBeInTheDocument();
+ });
+
+ it('should have a text input', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(ImportProject);
+
+ const name = screen.getByPlaceholderText(
+ enMessages.page.import_project.form.fields.name.placeholder
+ );
+
+ // Assert
+ expect(name).toBeInTheDocument();
+ expect(name).toHaveAttribute('type', 'text');
+ });
+
+ describe('when empty', () => {
+ it('should display an error', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(ImportProject);
+
+ // Assert
+ expect(
+ screen.getByText(enMessages.page.import_project.form.fields.name.error)
+ ).toBeInTheDocument();
+ });
+ });
+
+ // FIXME: Enable once the dev server is running
+ describe.skip('when filled', () => {
+ it('should hide the error', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(ImportProject);
+
+ // Assert
+ expect(
+ screen.getByText(enMessages.page.import_project.form.fields.name.error)
+ ).not.toBeInTheDocument();
+ });
+
+ it('should show a live updating hint', () => {
+ // Arrange
+ const value = 'Vervis';
+
+ // Act
+ render(ImportProject, { value });
+
+ // Assert
+ expect(screen.getByText(`domain.example/projects/${value}`)).toBeInTheDocument();
+ });
+ });
+ });
+
+ describe('description', () => {
+ it('should have a label', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(ImportProject);
+
+ const description = screen.getByLabelText(
+ enMessages.page.import_project.form.fields.description.label
+ );
+
+ // Assert
+ expect(description).toBeInTheDocument();
+ });
+
+ it('should have a textarea', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(ImportProject);
+
+ const description = screen.getByPlaceholderText(
+ enMessages.page.import_project.form.fields.description.placeholder
+ );
+
+ // Assert
+ expect(description).toBeInTheDocument();
+ });
+ });
+
+ describe('avatar', () => {
+ it('should have a heading', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(ImportProject);
+
+ const avatar = screen.getByRole('heading', {
+ level: 3,
+ name: enMessages.page.import_project.form.avatar
+ });
+
+ // Assert
+ expect(avatar).toBeInTheDocument();
+ });
+
+ it('should have an upload button', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(ImportProject);
+
+ // Turn into regular expression to account for the icon
+ const avatar = screen.getByLabelText(
+ new RegExp(enMessages.page.import_project.form.fields.avatar.label)
+ );
+
+ // Assert
+ expect(avatar).toBeInTheDocument();
+ });
+ });
+
+ describe('components', () => {
+ it('should have a heading', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(ImportProject);
+
+ const components = screen.getByRole('heading', {
+ level: 3,
+ name: enMessages.page.import_project.form.components
+ });
+
+ // Assert
+ expect(components).toBeInTheDocument();
+ });
+
+ describe('repository', () => {
+ it('should have a label', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(ImportProject);
+
+ // Turn into regular expression to account for the icon
+ const repository = screen.getByLabelText(
+ enMessages.page.import_project.form.fields.repository.label
+ );
+
+ // Assert
+ expect(repository).toBeInTheDocument();
+ });
+
+ it('should have an URL input', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(ImportProject);
+
+ // Turn into regular expression to account for the icon
+ const repository = screen.getByPlaceholderText(
+ enMessages.page.import_project.form.fields.repository.placeholder
+ );
+
+ // Assert
+ expect(repository).toBeInTheDocument();
+ });
+
+ it('should have a hint', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(ImportProject);
+
+ // Turn into regular expression to account for the icon
+ const repository = screen.getByText(
+ enMessages.page.import_project.form.fields.repository.hint
+ );
+
+ // Assert
+ expect(repository).toBeInTheDocument();
+ });
+ });
+
+ describe('issues', () => {
+ it('should have a label', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(ImportProject);
+
+ // Turn into regular expression to account for the icon
+ const issues = screen.getByLabelText(
+ enMessages.page.import_project.form.fields.issues.label
+ );
+
+ // Assert
+ expect(issues).toBeInTheDocument();
+ });
+ });
+
+ describe('pull requests', () => {
+ it('should have a label', () => {
+ // Arrange
+ // Nothing to prepare
+
+ // Act
+ render(ImportProject);
+
+ // Turn into regular expression to account for the icon
+ const issues = screen.getByLabelText(enMessages.page.import_project.form.fields.pr.label);
+
+ // Assert
+ expect(issues).toBeInTheDocument();
+ });
+ });
+ });
+});