@shopbite-de/storefront 1.2.8 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.dockerignore CHANGED
@@ -25,4 +25,6 @@ logs
25
25
 
26
26
 
27
27
  # Database
28
- db.json
28
+ db.json
29
+ coverage
30
+ test-results
@@ -12,7 +12,7 @@ jobs:
12
12
 
13
13
  steps:
14
14
  - name: Checkout repository
15
- uses: actions/checkout@v5
15
+ uses: actions/checkout@v6
16
16
 
17
17
  - name: Log in to the Container registry
18
18
  uses: docker/login-action@v3
@@ -46,3 +46,32 @@ jobs:
46
46
  push: true
47
47
  tags: ${{ steps.meta-app.outputs.tags }}
48
48
  labels: ${{ steps.meta-app.outputs.labels }}
49
+
50
+ release-npm:
51
+ runs-on: ubuntu-latest
52
+ needs: build-and-push
53
+ permissions:
54
+ contents: read
55
+ id-token: write
56
+ steps:
57
+ - name: Checkout repository
58
+ uses: actions/checkout@v6
59
+
60
+ - name: Install Node.js
61
+ uses: actions/setup-node@v6
62
+ with:
63
+ node-version: '24'
64
+ registry-url: 'https://registry.npmjs.org'
65
+
66
+ - name: Install pnpm
67
+ uses: pnpm/action-setup@v4
68
+ with:
69
+ version: 10.26.2
70
+
71
+ - name: Install dependencies
72
+ run: pnpm install --frozen-lockfile
73
+
74
+ - name: Publish to npm
75
+ run: pnpm publish --no-git-checks --access public
76
+ env:
77
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -5,55 +5,26 @@ on:
5
5
  pull_request:
6
6
  types: [opened, synchronize]
7
7
  jobs:
8
- init:
9
- name: Init dependecies
10
- timeout-minutes: 15
8
+ test:
9
+ name: Run unit and e2e tests
10
+ environment: test
11
11
  env:
12
+ NUXT_PUBLIC_APP_ENV: 'test'
13
+ NUXT_PUBLIC_STORE_URL: 'http://localhost:3005'
14
+ NUXT_PUBLIC_SHOPWARE_DEV_STORE_FRONT_URL: 'http://localhost:3005'
15
+ PORT: '3005'
12
16
  NUXT_PUBLIC_SHOPWARE_ENDPOINT: ${{ secrets.NUXT_PUBLIC_SHOPWARE_ENDPOINT }}
13
17
  NUXT_PUBLIC_SHOPWARE_ACCESS_TOKEN: ${{ secrets.NUXT_PUBLIC_SHOPWARE_ACCESS_TOKEN }}
14
- runs-on: ubuntu-latest
15
- steps:
16
- - name: Check out code
17
- uses: actions/checkout@v5
18
-
19
- - name: Install Node.js
20
- uses: actions/setup-node@v6
21
- with:
22
- node-version: '24'
23
-
24
- - name: Install pnpm
25
- uses: pnpm/action-setup@v4
26
-
27
- - name: Get pnpm store directory
28
- id: pnpm-cache
29
- shell: bash
30
- run: |
31
- echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
32
-
33
- - name: Setup pnpm cache
34
- uses: actions/cache@v4
35
- with:
36
- path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
37
- key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
38
- restore-keys: |
39
- ${{ runner.os }}-pnpm-store-
40
-
41
- - name: Install dependencies
42
- run: pnpm install --frozen-lockfile
43
-
44
- - name: Build
45
- env:
46
- NODE_OPTIONS: "--max_old_space_size=4096"
47
- run: pnpm run build
48
- test:
49
- name: Run Unit tests
18
+ NUXT_PUBLIC_SITE_COUNTRY_ID: ${{ secrets.NUXT_PUBLIC_SITE_COUNTRY_ID }}
19
+ GEOPIFY: ${{ secrets.GEOPIFY }}
20
+ TEST_USER: ${{ secrets.TEST_USER }}
21
+ TEST_USER_PASS: ${{ secrets.TEST_USER_PASS }}
50
22
  if: github.event.pull_request.draft == false
51
- needs: init
52
23
  timeout-minutes: 15
53
24
  runs-on: ubuntu-latest
54
25
  steps:
55
26
  - name: Check out code
56
- uses: actions/checkout@v5
27
+ uses: actions/checkout@v6
57
28
 
58
29
  - name: Fetch main
59
30
  run: git fetch origin main
@@ -66,7 +37,7 @@ jobs:
66
37
  - name: Enable corepack and pnpm
67
38
  run: |
68
39
  corepack enable
69
- corepack prepare pnpm@10.20.0 --activate
40
+ corepack prepare pnpm@10.26.2 --activate
70
41
 
71
42
  - name: Install build dependencies
72
43
  run: |
@@ -74,7 +45,7 @@ jobs:
74
45
  sudo apt-get install -y build-essential python3
75
46
 
76
47
  - name: Cache pnpm dependencies
77
- uses: actions/cache@v4
48
+ uses: actions/cache@v5
78
49
  with:
79
50
  path: |
80
51
  /home/runner/.local/share/pnpm/store/v3
@@ -85,18 +56,33 @@ jobs:
85
56
 
86
57
  - name: Install dependencies
87
58
  run: pnpm install --frozen-lockfile --prefer-offline
88
- working-directory: ${{ inputs.workdir }}
89
59
 
90
60
  - name: Rebuild native modules
91
61
  run: pnpm rebuild better-sqlite3
92
- working-directory: ${{ inputs.workdir }}
93
62
 
94
- - name: Unit test
63
+ - name: Build
95
64
  env:
96
- NUXT_PUBLIC_SHOPWARE_ENDPOINT: 'test'
97
- NUXT_PUBLIC_SHOPWARE_ACCESS_TOKEN: 'test'
98
- NUXT_PUBLIC_SHOPWARE_DEV_STOREFRONT_URL: 'test'
65
+ NODE_OPTIONS: "--max_old_space_size=4096"
66
+ run: pnpm run build
67
+
68
+ - name: Install Playwright Browsers
69
+ run: pnpm exec playwright install --with-deps chromium
70
+
71
+ - name: Unit tests
72
+ run: pnpm test:unit
73
+
74
+ - name: E2E tests
75
+ env:
76
+ TEST_USER: ${{ secrets.TEST_USER }}
77
+ TEST_USER_PASS: ${{ secrets.TEST_USER_PASS }}
78
+
79
+ run: pnpm test:e2e
80
+
81
+ - uses: actions/upload-artifact@v6
82
+ if: ${{ !cancelled() }}
83
+ with:
84
+ name: playwright-report
85
+ path: playwright-report/
86
+ retention-days: 30
99
87
 
100
- run: pnpm test
101
- working-directory: ${{ inputs.workdir }}
102
88
 
@@ -51,6 +51,7 @@ const mainIngredients = computed<Schemas["PropertyGroupOption"][]>(() => {
51
51
 
52
52
  <template>
53
53
  <AnimatedSection
54
+ :id="`product-card-${product.id}`"
54
55
  animation="fade-up"
55
56
  duration="duration-1000"
56
57
  delay="delay-100"
package/compose.yml CHANGED
@@ -9,9 +9,6 @@ services:
9
9
  user: node
10
10
  env_file:
11
11
  - .env
12
- volumes:
13
- - ".:/app"
14
- - "/app/node_modules"
15
12
  ports:
16
13
  - '3000:3000'
17
14
  - '24678:24678'
package/container CHANGED
File without changes
package/node.dockerfile CHANGED
@@ -1,6 +1,7 @@
1
+
1
2
  FROM node:24-alpine AS build
2
3
 
3
- ARG PNPM_VERSION=10.20.0
4
+ ARG PNPM_VERSION=10.26.2
4
5
  ARG NUXT_PUBLIC_SHOPWARE_ENDPOINT='https://my.shop/store-api'
5
6
  ARG NUXT_PUBLIC_SHOPWARE_ACCESS_TOKEN='TOKEN'
6
7
 
@@ -26,7 +27,11 @@ WORKDIR /app
26
27
 
27
28
  ENV NODE_ENV=production
28
29
 
29
- COPY --from=build /app/.output /app/.output
30
+ RUN mkdir -p /app/.data && chown -R node:node /app
31
+
32
+ COPY --from=build --chown=node:node /app/.output /app/.output
33
+
34
+ USER node
30
35
 
31
36
  EXPOSE 3000
32
37
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shopbite-de/storefront",
3
- "version": "1.2.8",
3
+ "version": "1.3.0",
4
4
  "main": "nuxt.config.ts",
5
5
  "description": "Shopware storefront for food delivery shops",
6
6
  "keywords": [
@@ -13,29 +13,13 @@
13
13
  "author": "@veliu",
14
14
  "license": "MIT",
15
15
  "type": "module",
16
- "scripts": {
17
- "build": "nuxt build",
18
- "dev": "nuxt dev",
19
- "generate": "nuxt generate",
20
- "preview": "nuxt preview",
21
- "postinstall": "nuxt prepare",
22
- "test": "vitest run --coverage",
23
- "test:ui": "vitest --ui",
24
- "eslint": "eslint .",
25
- "eslint:fix": "eslint --fix app/",
26
- "prettier": "prettier --check \"**/*.{ts,tsx,md,vue}\"",
27
- "prettier:fix": "prettier --check \"**/*.{ts,tsx,md,vue}\" --write",
28
- "generate-types": "shopware-api-gen generate --apiType=store",
29
- "load-schema": "bunx @shopware/api-gen loadSchema --apiType=store",
30
- "lint:fix": "npm run prettier:fix && npm run eslint:fix"
31
- },
32
16
  "dependencies": {
33
17
  "@headlessui/vue": "^1.7.23",
34
18
  "@heroicons/vue": "^2.2.0",
35
19
  "@iconify-json/lucide": "^1.2.73",
36
20
  "@iconify-json/simple-icons": "^1.2.59",
37
21
  "@nuxt/content": "^3.8.2",
38
- "@nuxt/image": "^1.11.0",
22
+ "@nuxt/image": "^2.0.0",
39
23
  "@nuxt/ui": "^4.1.0",
40
24
  "@nuxtjs/plausible": "2.0.1",
41
25
  "@nuxtjs/robots": "^5.5.6",
@@ -45,26 +29,50 @@
45
29
  "@shopware/composables": "^1.9.1",
46
30
  "@shopware/helpers": "^1.5.0",
47
31
  "@shopware/nuxt-module": "^1.4.1",
48
- "@tailwindcss/vite": "^4.1.17",
49
32
  "@unhead/vue": "^2.0.19",
50
33
  "@vite-pwa/nuxt": "^1.0.7",
51
- "@vueuse/core": "^13.9.0",
34
+ "@vueuse/core": "^14.0.0",
35
+ "dotenv": "^17.2.3",
52
36
  "nuxt": "^4.2.1",
53
37
  "uuid": "^13.0.0"
54
38
  },
55
39
  "devDependencies": {
40
+ "@nuxt/devtools-kit": "^3.1.1",
56
41
  "@nuxt/eslint": "^1.10.0",
57
42
  "@nuxt/test-utils": "^3.20.1",
43
+ "@playwright/test": "^1.57.0",
44
+ "@types/node": "^25.0.3",
58
45
  "@vitejs/plugin-vue": "^6.0.1",
59
- "@vitest/coverage-v8": "4.0.5",
46
+ "@vitest/coverage-v8": "4.0.16",
47
+ "@vitest/ui": "4.0.16",
48
+ "@vue/compiler-dom": "^3.5.26",
49
+ "@vue/server-renderer": "^3.5.26",
60
50
  "@vue/test-utils": "^2.4.6",
51
+ "dotenv-cli": "^11.0.0",
61
52
  "eslint": "^9.39.1",
62
53
  "happy-dom": "^20.0.10",
63
54
  "jsdom": "^27.2.0",
64
55
  "playwright-core": "^1.56.1",
65
56
  "prettier": "^3.6.2",
57
+ "tailwindcss": "^4.1.18",
66
58
  "typescript": "^5.9.3",
67
59
  "vitest": "^4.0.10"
68
60
  },
69
- "packageManager": "pnpm@10.20.0"
70
- }
61
+ "scripts": {
62
+ "build": "nuxt build",
63
+ "dev": "nuxt dev",
64
+ "generate": "nuxt generate",
65
+ "preview": "nuxt preview",
66
+ "postinstall": "nuxt prepare",
67
+ "test:unit": "vitest run --coverage",
68
+ "test:ui": "vitest --ui",
69
+ "test:e2e": "playwright test --project=chromium",
70
+ "eslint": "eslint .",
71
+ "eslint:fix": "eslint --fix app/",
72
+ "prettier": "prettier --check \"**/*.{ts,tsx,md,vue}\"",
73
+ "prettier:fix": "prettier --check \"**/*.{ts,tsx,md,vue}\" --write",
74
+ "generate-types": "shopware-api-gen generate --apiType=store",
75
+ "load-schema": "bunx @shopware/api-gen loadSchema --apiType=store",
76
+ "lint:fix": "npm run prettier:fix && npm run eslint:fix"
77
+ }
78
+ }
@@ -0,0 +1,77 @@
1
+ import { defineConfig, devices } from '@playwright/test';
2
+ import type { ConfigOptions } from "@nuxt/test-utils/playwright";
3
+ import { fileURLToPath } from "node:url";
4
+ import dotenv from "dotenv";
5
+ import path from 'path';
6
+
7
+ dotenv.config({ path: path.resolve(".env.test") });
8
+
9
+ /**
10
+ * See https://playwright.dev/docs/test-configuration.
11
+ */
12
+ export default defineConfig<ConfigOptions>({
13
+ use: {
14
+ baseURL: process.env.NUXT_PUBLIC_STORE_URL || "http://localhost:3000",
15
+ nuxt: {
16
+ rootDir: fileURLToPath(new URL(".", import.meta.url)),
17
+ },
18
+ trace: "on-first-retry",
19
+ },
20
+ testDir: "./test/e2e",
21
+ /* Run tests in files in parallel */
22
+ fullyParallel: true,
23
+ /* Fail the build on CI if you accidentally left test.only in the source code. */
24
+ forbidOnly: !!process.env.CI,
25
+ /* Retry on CI only */
26
+ retries: process.env.CI ? 2 : 0,
27
+ /* Opt out of parallel tests on CI. */
28
+ workers: process.env.CI ? 1 : undefined,
29
+ /* Reporter to use. See https://playwright.dev/docs/test-reporters */
30
+ reporter: "html",
31
+ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
32
+
33
+ /* Configure projects for major browsers */
34
+ projects: [
35
+ {
36
+ name: "chromium",
37
+ use: { ...devices["Desktop Chrome"] },
38
+ },
39
+
40
+ // {
41
+ // name: "firefox",
42
+ // use: { ...devices["Desktop Firefox"] },
43
+ // },
44
+ //
45
+ // {
46
+ // name: "webkit",
47
+ // use: { ...devices["Desktop Safari"] },
48
+ // },
49
+
50
+ /* Test against mobile viewports. */
51
+ // {
52
+ // name: 'Mobile Chrome',
53
+ // use: { ...devices['Pixel 5'] },
54
+ // },
55
+ // {
56
+ // name: 'Mobile Safari',
57
+ // use: { ...devices['iPhone 12'] },
58
+ // },
59
+
60
+ /* Test against branded browsers. */
61
+ // {
62
+ // name: 'Microsoft Edge',
63
+ // use: { ...devices['Desktop Edge'], channel: 'msedge' },
64
+ // },
65
+ // {
66
+ // name: 'Google Chrome',
67
+ // use: { ...devices['Desktop Chrome'], channel: 'chrome' },
68
+ // },
69
+ ],
70
+
71
+ /* Run your local dev server before starting the tests */
72
+ webServer: {
73
+ command: "node .output/server/index.mjs",
74
+ url: process.env.NUXT_PUBLIC_STORE_URL || "http://localhost:3000",
75
+ reuseExistingServer: !process.env.CI,
76
+ },
77
+ });
@@ -0,0 +1,149 @@
1
+ import { test, expect, type Page } from "@playwright/test";
2
+
3
+ test.beforeEach(async ({ page }) => {
4
+ await page.goto("/");
5
+ await page.evaluate(() => {
6
+ localStorage.clear();
7
+ sessionStorage.clear();
8
+ });
9
+ await page.context().clearCookies();
10
+ });
11
+
12
+ test("Simple Checkout As Recurring Customer", async ({ page }) => {
13
+ const QUANTITY_TO_ADD = 4;
14
+ const PRODUCT_ID = "019a4baa4267700b911411eca842ee17";
15
+
16
+ await clearCart(page);
17
+ await navigateToCategoryAndVerifyProducts(page);
18
+ await selectProductAndAddToCart(page, PRODUCT_ID, QUANTITY_TO_ADD);
19
+ await proceedToCheckoutAndLogin(page);
20
+ await verifyCheckoutQuantity(page);
21
+ await selectPaymentAndShipping(page);
22
+ await proceedToOrderReview(page);
23
+ await page.goto("/");
24
+ await clearCart(page);
25
+ });
26
+
27
+ // Helper Functions
28
+
29
+ async function clearCart(page: Page) {
30
+ const cartButton = page
31
+ .locator("button .i-lucide\\:shopping-cart")
32
+ .locator("..");
33
+
34
+ await expect(cartButton).toBeVisible({ timeout: 5000 });
35
+ await cartButton.click();
36
+
37
+ const cartDrawer = page.locator('[data-vaul-drawer][data-state="open"]');
38
+ await expect(cartDrawer).toBeVisible({ timeout: 5000 });
39
+ await expect(page.getByRole("heading", { name: /warenkorb/i })).toBeVisible();
40
+
41
+ const deleteButtons = page.getByRole("button", {
42
+ name: "Remove item from cart",
43
+ });
44
+ const itemCount = await deleteButtons.count();
45
+
46
+ for (let i = itemCount - 1; i >= 0; i--) {
47
+ await deleteButtons.nth(i).click();
48
+ await page.waitForTimeout(500);
49
+ }
50
+ }
51
+
52
+ async function navigateToCategoryAndVerifyProducts(page: Page) {
53
+ await page.goto("/c/Pizza", { waitUntil: "load" });
54
+ await expect(page.locator("h1")).toHaveText("Pizza");
55
+
56
+ const productCards = page.locator('[id^="product-card-"]');
57
+ await expect(productCards).toHaveCount(5, { timeout: 10000 });
58
+ }
59
+
60
+ async function selectProductAndAddToCart(
61
+ page: Page,
62
+ productId: string,
63
+ quantity: number,
64
+ ) {
65
+ const productCard = page.locator(`#product-card-${productId}`);
66
+ await expect(productCard).toBeVisible();
67
+
68
+ // Verify product card structure
69
+ const buttons = productCard.locator("button");
70
+ await expect(buttons).toHaveCount(2);
71
+ await expect(productCard.locator("button .i-lucide\\:heart")).toBeVisible();
72
+
73
+ // Open product details
74
+ const showDetailsButton = productCard.locator(
75
+ "button .i-lucide\\:shopping-cart",
76
+ );
77
+ await expect(showDetailsButton).toBeVisible();
78
+ await showDetailsButton.click();
79
+
80
+ // Set quantity and add to cart
81
+ const quantityInput = page.getByRole("spinbutton", { name: /anzahl/i });
82
+ await expect(quantityInput).toBeVisible();
83
+ await quantityInput.fill(quantity.toString());
84
+ await expect(quantityInput).toHaveValue(quantity.toString());
85
+
86
+ const addToCartButton = page.getByRole("button", {
87
+ name: "In den Warenkorb",
88
+ });
89
+ await expect(addToCartButton).toBeVisible({ timeout: 10000 });
90
+ await addToCartButton.click();
91
+ await expect(addToCartButton).not.toBeVisible({ timeout: 5000 });
92
+ }
93
+
94
+ async function proceedToCheckoutAndLogin(page: Page) {
95
+ await page.goto("/bestellung", { waitUntil: "load" });
96
+
97
+ const loginTab = page.getByRole("tab", { name: "Einloggen" });
98
+ await expect(loginTab).toBeVisible();
99
+ await loginTab.click();
100
+
101
+ // Fill login form
102
+ const loginEmailInput = page.getByPlaceholder("Email-Adresse eingeben");
103
+ const loginPasswordInput = page.getByPlaceholder("Passwort eingeben");
104
+ const loginButton = page.getByRole("button", { name: "Anmelden" });
105
+
106
+ await expect(loginEmailInput).toBeVisible();
107
+ await expect(loginPasswordInput).toBeVisible();
108
+ await expect(loginButton).toBeVisible();
109
+
110
+ await loginEmailInput.fill(process.env.TEST_USER!);
111
+ await loginPasswordInput.fill(process.env.TEST_USER_PASS!);
112
+ await loginButton.click();
113
+ }
114
+
115
+ async function verifyCheckoutQuantity(page: Page) {
116
+ const checkoutQuantityInput = page.getByRole("spinbutton", {
117
+ name: /item quantity/i,
118
+ });
119
+ await expect(checkoutQuantityInput).toBeVisible();
120
+ // Quantity verification can be added here if needed
121
+ }
122
+
123
+ async function selectPaymentAndShipping(page: Page) {
124
+ const nextStepButton = page.getByRole("button", {
125
+ name: "Zahlungs- und Versandart auswählen",
126
+ });
127
+ await expect(nextStepButton).toBeVisible({ timeout: 10000 });
128
+ await expect(nextStepButton).toBeEnabled();
129
+ await nextStepButton.click();
130
+
131
+ // Verify payment method
132
+ const paymentRadio = page.getByRole("radio", { name: "Cash on delivery" });
133
+ await expect(paymentRadio).toBeVisible();
134
+ await expect(paymentRadio).toBeChecked();
135
+
136
+ // Verify delivery method
137
+ const deliveryRadio = page.getByRole("radio", { name: "Standard" });
138
+ await expect(deliveryRadio).toBeVisible();
139
+ await expect(deliveryRadio).toBeChecked();
140
+ }
141
+
142
+ async function proceedToOrderReview(page: Page) {
143
+ const lastStepButton = page.getByRole("button", {
144
+ name: "Weiter zu Prüfen & Bestellen",
145
+ });
146
+ await expect(lastStepButton).toBeVisible({ timeout: 10000 });
147
+ await expect(lastStepButton).toBeEnabled();
148
+ await lastStepButton.click();
149
+ }