PluginCi 1.1.15 Help

Storefront

Testing von Shopware-Storefront Modulen stellt sich dar wie bei jedem anderen Javascript/Typescript Projekt.

Gesetz dem Falle natürlich, man verwendet keine weiteren dependencies die das Testen mit konventionellen mitteln als Schwierig darstellt (z.B. Vue)

Für das Testing sind natürlich weitere Abhängigkeiten notwendig:

{ "scripts": { "test": "jest --config jest.config.js --ci", "test-watch": "jest --config jest.config.js --watch" }, "devDependencies": { "@babel/plugin-transform-class-properties": "^7.18.6", "@babel/plugin-transform-runtime": "^7.19.6", "@babel/preset-env": "^7.23.9", "@types/bootstrap": "^5.2.7", "@types/jest": "^29.5.12", "@types/utf8": "^3.0.3", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "jest-junit": "^16.0.0", "ts-jest": "^29.1.2", "tslib": "^2.6.2", "typescript": "^5.3.3" } }

Sollte Vue verwendet werden durch die Nutzung von Komponenten aus dem BasePlugin-Storefront, kommen noch weitere Elemente hinzu

{ "devDependencies": { "node-fetch": "^2.7.0", "jest-mock-server": "^0.1.0", "@types/node-fetch": "^3.0.3", "@vue/test-utils": "^2.4.4", "flush-promises": "^1.0.2" } }

Um Jest mit Typescript und Klassen verwenden zu können, wird Babel entsprechend konfiguriert:

module.exports = { presets: ["@babel/preset-env"], plugins: [ "@babel/plugin-transform-class-properties", "@babel/plugin-transform-runtime", ], };

Jest muss natürlich auch noch Konfiguriert sein:

const { resolve } = require("path"); process.env.STOREFRONT_PATH = process.env.STOREFRONT_PATH || resolve( "../../../../../../../vendor/shopware/storefront/Resources/app/storefront" ); process.env.BASEPLUGIN_PATH = process.env.BASEPLUGIN_PATH || resolve("../../../../../BasePlugin"); module.exports = { preset: "ts-jest", transform: { "^.+\\.(ts|tsx)?$": "ts-jest", "^.+\\.(js|jsx)$": "babel-jest", }, testEnvironment: "jsdom", globals: { storefrontPath: process.env.STOREFRONT_PATH, }, coverageThreshold: { global: { branches: 50, functions: 90, lines: 90, statements: 90, }, }, reporters: [ "default", ["jest-junit", { outputDirectory: "reports", outputName: "report.xml" }], ], collectCoverage: true, coverageReporters: ["lcov", "text", "clover"], testRegex: "/test/.*\\.(test|spec)?\\.(ts|tsx)$", collectCoverageFrom: [ "<rootDir>/src/**/*.ts", "!<rootDir>/src/**/*.spec.ts", "!<rootDir>/src/**/*.d.ts", ], moduleDirectories: ["node_modules"], moduleNameMapper: { "^bootstrap(.*)$": `${process.env.STOREFRONT_PATH}/node_modules/bootstrap$1`, "^src(.*)$": `${process.env.STOREFRONT_PATH}/src/$1`, "^@LeopardenBasePlugin(.*)$": `${process.env.BASEPLUGIN_PATH}/src/Resources/app/storefront/src$1`, }, testEnvironmentOptions: { customExportConditions: ["node", "node-addons"], }, setupFilesAfterEnv: [ `${process.env.STOREFRONT_PATH}/jest.init.js`, `${process.env.BASEPLUGIN_PATH}/src/Resources/app/storefront/test/jest.init.ts`, ], };

Manche Elemente, die von Shopware geliefert werden, werden anhand der initialisierungsdatei von BasePlugin/Storefront schon erledigt. Man kann aber auch in folgender Datei weiteres Anpassen

global.console = { ...console, // uncomment to ignore a specific log level // log: jest.fn(), debug: jest.fn(), info: jest.fn(), warn: jest.fn(), // error: jest.fn(), };

Zuletzt muss der Typescript Compiler noch ein paar wenige Einstellungen erhalten, damit zum einen Code-Highlighting in PHP-Storm korrekt funktioniert als auch Jest die korrekten Pfade kennt.

"importHelpers": true, "skipLibCheck": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "outDir": "./dist/storefront/js", "noImplicitAny": false, "allowJs": true, "baseUrl": ".", "sourceMap": true, "resolveJsonModule": true, "moduleResolution": "node", "paths": { "src/*": [ "../../../../../../../vendor/shopware/storefront/Resources/app/storefront/src/*" ], "vendor/*": [ "../../../../../../../vendor/shopware/storefront/Resources/app/storefront/vendor/*" ], "@LeopardenBasePlugin/*": [ "../../../../../BasePlugin/src/Resources/app/storefront/src/*" ], "vue/*": [ "../../../../../BasePlugin/src/Resources/app/storefront/node_modules/vue/*" ] } }, "stats": { "errorDetails": true }, "files": [ "../../../../../BasePlugin/src/Resources/app/storefront/shims.window.d.ts" ], "include": [ "./**/*.ts" ], "exclude": [ "node_modules" ] }

Sollte das BasePlugin nur als Dependency installiert worden sein, so muss natürlich der Pfad entsprechend auf <project_root>/vendor/leoparden/baseplugin/src/Resources/app/storefront/<file> zeigen

Mit npm install und npm run test-watch kann nun das Testen beginnen.

Ein Beispiel-Test:

import { mount } from "@vue/test-utils"; import { resolve } from "path"; import Pagination from "@LeopardenBasePlugin/lib/Pagination"; import { nextTick } from "vue"; describe("%pluginname%", () => { let plugin: %pluginname%Plugin, app; const onResponse = (): Promise<EntityResponse<{id: string, productNumber:string}>> => new Promise((resolve) => { plugin.$emitter.subscribe( "onResponse", async () => { await nextTick(); resolve(<EntityResponse<{id: string, productNumber:string}>>plugin.response); }, { once: true } ); }); const setData = async (): Promise<EntityResponse<{id: string, productNumber:string}>> => { const waiter = onResponse(); plugin.refresh(); return await waiter; }; const options = { url: resolve("./test/fixtures/product.json"), sorting: { field: "productNumber", }, baseUrl: "http://www.test.com", }; beforeEach(async () => { window.PluginManager.register( %pluginname%Plugin.pluginName, %pluginname%Plugin, %pluginname%Plugin.selector ); const element = document.createElement("div"); element.dataset.%pluginname% = "true"; element.dataset.%pluginname%Options = JSON.stringify(options); document.body.appendChild(element); const init: Promise<%pluginname%Plugin> = new Promise((resolve) => { element.addEventListener("onInit", (event: CustomEvent) => resolve(event.detail) ); }); new %pluginname%Plugin(element, options, %pluginname%Plugin.pluginName); plugin = await init; plugin.pagination = new Pagination(plugin); app = mount({ ...plugin.appOptions(plugin), template: "<div>{{response?.elements}}{{plugin.domain}}<table><tr v-for='product in response?.elements'><td :data-id='product.id'><a :href='plugin.getURL(product)'>{{product.name}}</a></td></tr></table></div>", }); }); it("should be a vue component", () => { expect(app.vm).toBeTruthy(); }); it("should load products", async () => { const response = await setData(); expect(plugin.categories).toBeInstanceOf(Map); expect(plugin.getURL(plugin.categories.get("1").products[0])).toEqual( `${options.baseUrl}/${response.elements[0].seoUrls[0].pathInfo}` ); expect(plugin.getURL(plugin.categories.get("1").products[1])).toEqual( `${options.baseUrl}/detail/${response.elements[1].id}` ); }); });
Last modified: 29 February 2024