diff --git a/app/.gitignore b/app/.gitignore index e3471e4..38b6f41 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -10,3 +10,4 @@ vite.config.js.timestamp-* vite.config.ts.timestamp-* couchdb shopping +test-results diff --git a/app/package.json b/app/package.json index 616a6f1..4af4732 100644 --- a/app/package.json +++ b/app/package.json @@ -51,5 +51,9 @@ "vitest": "^1.2.0", "yaml": "^2.4.1" }, - "type": "module" + "type": "module", + "dependencies": { + "@types/speakingurl": "^13.0.6", + "speakingurl": "^14.0.1" + } } diff --git a/app/playwright.config.ts b/app/playwright.config.ts index 3cf8326..94a4675 100644 --- a/app/playwright.config.ts +++ b/app/playwright.config.ts @@ -3,7 +3,8 @@ import type { PlaywrightTestConfig } from '@playwright/test'; const config: PlaywrightTestConfig = { webServer: { command: 'pnpm run build && pnpm run preview', - port: 4173 + port: 4173, + reuseExistingServer: true }, testDir: 'tests', testMatch: /(.+\.)?(test|spec)\.[jt]s/ diff --git a/app/pnpm-lock.yaml b/app/pnpm-lock.yaml index 6df9455..db7fc4b 100644 --- a/app/pnpm-lock.yaml +++ b/app/pnpm-lock.yaml @@ -4,6 +4,14 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +dependencies: + '@types/speakingurl': + specifier: ^13.0.6 + version: 13.0.6 + speakingurl: + specifier: ^14.0.1 + version: 14.0.1 + devDependencies: '@iconify-json/flowbite': specifier: ^1.1.2 @@ -904,6 +912,10 @@ packages: resolution: {integrity: sha512-/wdoPq1QqkSj9/QOeKkFquEuPzQbHTWAMPH/PaUMB+JuR31lXhlWXRZ52IpfDYVlDOUBvX09uBrPwxGT1hjNBg==} dev: true + /@types/speakingurl@13.0.6: + resolution: {integrity: sha512-ywkRHNHBwq0mFs/2HRgW6TEBAzH66G8f2Txzh1aGR0UC9ZoAUHfHxLZGDhwMpck4BpSnB61eNFIFmlV+TJ+KUA==} + dev: false + /@typescript-eslint/eslint-plugin@7.0.2(@typescript-eslint/parser@7.0.2)(eslint@8.56.0)(typescript@5.3.3): resolution: {integrity: sha512-/XtVZJtbaphtdrWjr+CJclaCVGPtOdBpFEnvtNf/jRV0IiEemRrL0qABex/nEt8isYcnFacm3nPHYQwL+Wb7qg==} engines: {node: ^16.0.0 || >=18.0.0} @@ -3205,6 +3217,11 @@ packages: resolution: {integrity: sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==} dev: true + /speakingurl@14.0.1: + resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} + engines: {node: '>=0.10.0'} + dev: false + /stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} dev: true diff --git a/app/src/lib/components/ListeErstellenForm.svelte b/app/src/lib/components/ListeErstellenForm.svelte deleted file mode 100644 index 1dc25ab..0000000 --- a/app/src/lib/components/ListeErstellenForm.svelte +++ /dev/null @@ -1,49 +0,0 @@ - - -
- - - - - - - {#if artikel.length > 0} - - {/if} - - -
diff --git a/app/src/lib/components/ListenItem.svelte b/app/src/lib/components/ListenItem.svelte deleted file mode 100644 index 77b23c0..0000000 --- a/app/src/lib/components/ListenItem.svelte +++ /dev/null @@ -1,15 +0,0 @@ - - -
-

{liste.titel}

- - // Buttons für Bearbeiten und Löschen hier einfügen -
diff --git "a/app/src/lib/components/Listen\303\234bersicht.svelte" "b/app/src/lib/components/Listen\303\234bersicht.svelte" deleted file mode 100644 index 2805a91..0000000 --- "a/app/src/lib/components/Listen\303\234bersicht.svelte" +++ /dev/null @@ -1,18 +0,0 @@ - - -
- {#each listen as liste} - - {/each} -
diff --git a/app/src/lib/components/Product.svelte b/app/src/lib/components/Product.svelte new file mode 100644 index 0000000..4f47108 --- /dev/null +++ b/app/src/lib/components/Product.svelte @@ -0,0 +1,39 @@ + + + +
+ {product.name} +
+
+ {product.count} + Stück +
+ +
diff --git a/app/src/lib/components/ProductList.svelte b/app/src/lib/components/ProductList.svelte index d7489eb..d8fd2d1 100644 --- a/app/src/lib/components/ProductList.svelte +++ b/app/src/lib/components/ProductList.svelte @@ -1,10 +1,48 @@ - {productlist.name} +
+ {productlist.name} + +
+ + +
+ +

+ Sicher, dass sie dieses Produkt löschen möchten? +

+ + +
+
diff --git a/app/src/lib/db.ts b/app/src/lib/db.ts index 30e79c6..05bb489 100644 --- a/app/src/lib/db.ts +++ b/app/src/lib/db.ts @@ -20,4 +20,11 @@ export interface ProductList { name: string; } -export const db = writable(new PouchDB('shopping')); +export interface Product { + type: 'product'; + list: string; + name: string; + count: number; +} + +export const db = writable(new PouchDB('shopping')); diff --git a/app/src/lib/index.ts b/app/src/lib/index.ts index 856f2b6..cfe6c83 100644 --- a/app/src/lib/index.ts +++ b/app/src/lib/index.ts @@ -1 +1,2 @@ // place files you want to import through the `$lib` alias in this folder. +export { default as getSlug } from 'speakingurl'; diff --git a/app/src/routes/+page.svelte b/app/src/routes/+page.svelte index a7a0b2f..14adb0e 100644 --- a/app/src/routes/+page.svelte +++ b/app/src/routes/+page.svelte @@ -1,16 +1,31 @@ @@ -62,4 +91,32 @@ {:catch error} Ein Fehler ist aufgetreten: {error.message} {/await} + + + + + +
+ Neue Einkaufsliste erstellen + + + +
+
diff --git a/app/src/routes/createlist/+page.svelte b/app/src/routes/createlist/+page.svelte deleted file mode 100644 index c3356c0..0000000 --- a/app/src/routes/createlist/+page.svelte +++ /dev/null @@ -1,35 +0,0 @@ - - -
-

Neue Einkaufsliste erstellen

- -
-
- - -
- - -
-
diff --git a/app/src/routes/items/[id]/+layout.ts b/app/src/routes/items/[id]/+layout.ts new file mode 100644 index 0000000..ae88a27 --- /dev/null +++ b/app/src/routes/items/[id]/+layout.ts @@ -0,0 +1,2 @@ +export const prerender = false; +export const ssr = false; diff --git a/app/src/routes/items/[id]/+page.svelte b/app/src/routes/items/[id]/+page.svelte new file mode 100644 index 0000000..6e13c25 --- /dev/null +++ b/app/src/routes/items/[id]/+page.svelte @@ -0,0 +1,123 @@ + + + + G'schäft'lhaberer + + +G'schäft'lHaberer + +
+ {#await data.products} + + {:then} + {#each products as product} + + {:else} +

Keine Produkte vorhanden

+ {/each} + {:catch error} + Ein Fehler ist aufgetreten: {error.message} + {/await} +
+ +
+ +
+ + + Produkt erstellen +
+ + +
+ + +
+
+
diff --git a/app/src/routes/items/[id]/+page.ts b/app/src/routes/items/[id]/+page.ts new file mode 100644 index 0000000..979fb47 --- /dev/null +++ b/app/src/routes/items/[id]/+page.ts @@ -0,0 +1,32 @@ +import { browser } from '$app/environment'; +import { db as DB } from '$lib/db'; +import { get } from 'svelte/store'; +import type { PageLoad } from './$types'; + +export const load: PageLoad = async ({ params: { id } }) => { + if (!browser) { + return { + products: Promise.resolve({ docs: [] }), + id + }; + } + + const db = get(DB); + const products = db + .createIndex({ + index: { fields: ['type', 'list'] } + }) + .then(async () => { + const query = { + selector: { + type: 'product', + list: id + } + }; + return await db.find(query); + }); + return { + products, + id + }; +}; diff --git a/app/src/routes/listen/+page.svelte b/app/src/routes/listen/+page.svelte deleted file mode 100644 index e69de29..0000000 diff --git a/app/src/routes/listen/erstellen/+page.svelte b/app/src/routes/listen/erstellen/+page.svelte deleted file mode 100644 index e69de29..0000000 diff --git a/app/src/routes/settings/+page.svelte b/app/src/routes/settings/+page.svelte index abc22d1..3fd45b4 100644 --- a/app/src/routes/settings/+page.svelte +++ b/app/src/routes/settings/+page.svelte @@ -1,3 +1,74 @@ + + Settings - G'schäft'lhaberer + +
+ Settings +
+ + + + {#if storedLink} +
+

Gespeicherter Link: {storedLink}

+ +
+ {/if} +
+
diff --git a/app/tests/create-lists.test.ts b/app/tests/create-lists.test.ts deleted file mode 100644 index 16cd7ae..0000000 --- a/app/tests/create-lists.test.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { expect, test } from '@playwright/test'; - -test('createlist page has expected h1', async ({ page }) => { - await page.goto('/createlist'); - await expect(page.getByRole('heading', { name: 'Neue Einkaufsliste erstellen' })).toBeVisible(); -}); - -test('createlist page has expected input', async ({ page }) => { - await page.goto('/createlist'); - await expect(page.getByRole('textbox', { name: 'Name' })).toBeVisible(); -}); diff --git a/app/tests/lists.test.ts b/app/tests/lists.test.ts index ef80f88..9ae6105 100644 --- a/app/tests/lists.test.ts +++ b/app/tests/lists.test.ts @@ -1,14 +1,47 @@ import { expect, test } from '@playwright/test'; -import PouchDB from 'pouchdb'; -import PouchDBFind from 'pouchdb-find'; -PouchDB.plugin(PouchDBFind); - -// async function clearDatabase() { -// const docs = await DB.allDocs(); -// docs.rows.forEach((row) => DB.remove(row.id, row.value.rev)); -// } test('no shopping lists entries', async ({ page }) => { await page.goto('/'); await expect(page.getByText('Keine Listen vorhanden')).toBeVisible(); }); + +test('create a list', async ({ page }) => { + const listName = 'Test'; + + await page.goto('/'); + const newButton = page.getByRole('button', { name: 'Neue Liste erstellen' }); + await expect(newButton).toBeVisible(); + await newButton.click(); + const nameInput = page.getByRole('textbox', { name: 'Name' }); + await expect(nameInput).toBeVisible(); + await nameInput.fill('Test'); + const confirmButton = page.getByRole('button', { name: 'Liste hinzufügen' }); + await expect(confirmButton).toBeVisible(); + await confirmButton.click(); + const listLink = page.getByRole('link', { name: listName }); + await expect(listLink).toBeVisible(); + await expect(listLink).toHaveAttribute('href', '/items/test'); +}); + +test('delete a list', async ({ page }) => { + const listName = 'Test'; + + await page.goto('/'); + const newButton = page.getByRole('button', { name: 'Neue Liste erstellen' }); + await newButton.click(); + const nameInput = page.getByRole('textbox', { name: 'Name' }); + await nameInput.fill('Test'); + const confirmCreationButton = page.getByRole('button', { name: 'Liste hinzufügen' }); + await confirmCreationButton.click(); + + const listLink = page.getByRole('link', { name: listName }); + expect(listLink).toBeVisible(); + const deleteButton = listLink.getByRole('button'); + await deleteButton.waitFor(); + await expect(deleteButton).toBeVisible(); + await deleteButton.click(); + const confirmDeletionButton = page.getByRole('button', { name: 'Ja, ich bin sicher' }); + await expect(confirmDeletionButton).toBeVisible(); + await confirmDeletionButton.click(); + await expect(page.getByText('Keine Listen vorhanden')).toBeVisible(); +}); diff --git a/app/tests/products.test.ts b/app/tests/products.test.ts new file mode 100644 index 0000000..c33050f --- /dev/null +++ b/app/tests/products.test.ts @@ -0,0 +1,67 @@ +import { expect, test, type Page } from '@playwright/test'; + +async function createList(page: Page) { + await page.goto('/'); + const newButton = page.getByRole('button', { name: 'Neue Liste erstellen' }); + await newButton.click(); + const nameInput = page.getByRole('textbox', { name: 'Name' }); + await nameInput.fill('Test'); + const confirmCreationButton = page.getByRole('button', { name: 'Liste hinzufügen' }); + await confirmCreationButton.click(); +} + +test('test no products', async ({ page }) => { + await createList(page); + + await page.goto('/items/test'); + await expect(page.getByText('Keine Produkte vorhanden')).toBeVisible(); +}); + +test('create a product', async ({ page }) => { + const productName = 'Testprodukt'; + + await createList(page); + + await page.goto('/items/test'); + const newButton = page.getByRole('button', { name: 'Produkt erstellen' }); + await expect(newButton).toBeVisible(); + await newButton.click(); + + const nameInput = page.getByRole('textbox', { name: 'Produktname' }); + await expect(nameInput).toBeVisible(); + await nameInput.fill(productName); + const countInput = page.getByLabel('Anzahl'); + await expect(countInput).toBeVisible(); + await countInput.fill('4'); + const confirmButton = page.getByRole('button', { name: 'Erstellen', exact: true }); + await expect(confirmButton).toBeVisible(); + await confirmButton.click(); + + const productLabel = page.getByRole('heading', { name: productName }); + await expect(productLabel).toBeVisible(); + const productCountLabel = page.getByText('4 Stück'); + await expect(productCountLabel).toBeVisible(); +}); + +test('delete a product', async ({ page }) => { + const productName = 'Testprodukt'; + + await createList(page); + + await page.goto('/items/test'); + const newButton = page.getByRole('button', { name: 'Produkt erstellen' }); + await newButton.click(); + + const nameInput = page.getByRole('textbox', { name: 'Produktname' }); + await nameInput.fill(productName); + const countInput = page.getByLabel('Anzahl'); + await countInput.fill('4'); + const confirmButton = page.getByRole('button', { name: 'Erstellen', exact: true }); + await confirmButton.click(); + + const deleteButton = page.getByRole('button').first(); + await expect(deleteButton).toBeVisible(); + await deleteButton.click(); + + await expect(page.getByText('Keine Produkte vorhanden')).toBeVisible(); +});