@scayle/storefront-product-detail 1.0.1 → 1.1.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/CHANGELOG.md +74 -0
- package/LICENSE +1 -1
- package/dist/module.json +2 -2
- package/dist/module.mjs +4 -2
- package/dist/runtime/composables/useProductSeoData.d.ts +11 -6
- package/dist/runtime/composables/useProductSeoData.js +11 -8
- package/dist/runtime/utils/product.d.ts +1 -1
- package/dist/runtime/utils/product.js +1 -3
- package/dist/runtime/utils/seo.d.ts +14 -6
- package/dist/runtime/utils/seo.js +38 -18
- package/package.json +15 -14
- package/dist/runtime/composables/useProductSeoData.test.d.ts +0 -1
- package/dist/runtime/composables/useProductSeoData.test.js +0 -179
- package/dist/runtime/utils/attribute.test.d.ts +0 -1
- package/dist/runtime/utils/attribute.test.js +0 -39
- package/dist/runtime/utils/product.test.d.ts +0 -1
- package/dist/runtime/utils/product.test.js +0 -97
- package/dist/runtime/utils/seo.test.d.ts +0 -1
- package/dist/runtime/utils/seo.test.js +0 -38
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# @scayle/storefront-product-detail
|
|
2
|
+
|
|
3
|
+
## 1.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- **\[SEO\]** Update product JSON-LD data to use the `ProductGroup` schema.
|
|
8
|
+
|
|
9
|
+
## 1.0.2
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Ensure the module meta has the correct package version
|
|
14
|
+
|
|
15
|
+
## 1.0.1
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- We've updated to `nuxt@3.14`
|
|
20
|
+
|
|
21
|
+
## 1.0.0
|
|
22
|
+
|
|
23
|
+
### Major Changes
|
|
24
|
+
|
|
25
|
+
- This release introduces the `@scayle/storefront-product-detail` package, decoupling composables and utilities of the Product Detail Page (PDP) functionality from the SCAYLE Storefront Boilerplate for enhanced modularity and integration.
|
|
26
|
+
This separation empowers developers with greater control over PDP updates and simplifies its integration into existing Storefront-based projects.
|
|
27
|
+
|
|
28
|
+
- [Discover more about the Product Detail Page for SCAYLE Storefront in our SCAYLE Resource Center.](https://scayle.dev/en/storefront-guide/developer-guide/features/pages/product-detail-page)
|
|
29
|
+
|
|
30
|
+
- This release requires `@scayle/storefront-nuxt@8.x` or higher. Support for `@scayle/storefront-nuxt@7.x` has been discontinued. Please update your dependencies accordingly. The `peerDependency` range has been updated to `^8.0.0`.
|
|
31
|
+
|
|
32
|
+
### Minor Changes
|
|
33
|
+
|
|
34
|
+
- Compared to the original implementation within the SCAYLE Storefront Boilerplate, the `@scayle/storefront-product-detail` package has received some improvements and refactoring:
|
|
35
|
+
|
|
36
|
+
- Introduced the `getCombineWithProductIds` function within the product utility, enabling efficient retrieval of product combinations within product listings.
|
|
37
|
+
- Added the `getFilteredAttributeGroups` function to the attribute utility, simplifying the retrieval and formatting of filtered product attributes for display in listings.
|
|
38
|
+
- Introduced the `useProductSeoData` composable, automating the generation of SEO-critical metadata for product pages. This includes dynamically generated canonical links, robots meta tags, product titles, and structured data (`JSON-LD`) for product details and breadcrumbs, enhancing search engine visibility.
|
|
39
|
+
|
|
40
|
+
## 0.4.0
|
|
41
|
+
|
|
42
|
+
### Minor Changes
|
|
43
|
+
|
|
44
|
+
- Fix computed `sortLinks` return type mismatch in `UseProductListSortReturn` interface.
|
|
45
|
+
|
|
46
|
+
## 0.3.0
|
|
47
|
+
|
|
48
|
+
### Minor Changes
|
|
49
|
+
|
|
50
|
+
- Fix `sortLinks` type by extending the `SelectedSort` type with `to` property which represents the `RouteLocationRaw` type from `vue-router`.
|
|
51
|
+
|
|
52
|
+
## 0.2.0
|
|
53
|
+
|
|
54
|
+
### Minor Changes
|
|
55
|
+
|
|
56
|
+
- Add and expose explicit return types/interfaces externally for all product detail composables.
|
|
57
|
+
|
|
58
|
+
## 0.1.2
|
|
59
|
+
|
|
60
|
+
### Patch Changes
|
|
61
|
+
|
|
62
|
+
- Update import of test factories to use new entry point
|
|
63
|
+
|
|
64
|
+
## 0.1.1
|
|
65
|
+
|
|
66
|
+
### Patch Changes
|
|
67
|
+
|
|
68
|
+
- Resolved issue where sorting did not correctly route to the first page.
|
|
69
|
+
|
|
70
|
+
## 0.1.0
|
|
71
|
+
|
|
72
|
+
### Minor Changes
|
|
73
|
+
|
|
74
|
+
- Introduce `@scayle/storefront-product-listing` package
|
package/LICENSE
CHANGED
package/dist/module.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scayle/storefront-product-detail",
|
|
3
3
|
"configKey": "product-detail",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.1.0",
|
|
5
5
|
"compatibility": {
|
|
6
6
|
"bridge": false,
|
|
7
7
|
"nuxt": ">=3.13"
|
|
8
8
|
},
|
|
9
9
|
"builder": {
|
|
10
10
|
"@nuxt/module-builder": "0.8.4",
|
|
11
|
-
"unbuild": "3.
|
|
11
|
+
"unbuild": "3.3.1"
|
|
12
12
|
}
|
|
13
13
|
}
|
package/dist/module.mjs
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { defineNuxtModule, createResolver, addImportsDir } from '@nuxt/kit';
|
|
2
2
|
|
|
3
|
+
const PACKAGE_NAME = "@scayle/storefront-product-detail";
|
|
4
|
+
const PACKAGE_VERSION = "1.1.0";
|
|
3
5
|
const module = defineNuxtModule({
|
|
4
6
|
meta: {
|
|
5
|
-
name:
|
|
7
|
+
name: PACKAGE_NAME,
|
|
6
8
|
configKey: "product-detail",
|
|
7
|
-
version:
|
|
9
|
+
version: PACKAGE_VERSION,
|
|
8
10
|
compatibility: {
|
|
9
11
|
bridge: false,
|
|
10
12
|
nuxt: ">=3.13"
|
|
@@ -1,19 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { BreadcrumbItem
|
|
3
|
-
import type { WithContext,
|
|
1
|
+
import type { ComputedRef, MaybeRefOrGetter } from 'vue';
|
|
2
|
+
import type { BreadcrumbItem } from '@scayle/storefront-nuxt';
|
|
3
|
+
import type { WithContext, BreadcrumbList, ProductGroup, Product } from 'schema-dts';
|
|
4
4
|
type CanonicalLink = Record<'rel' | 'key' | 'href', string>;
|
|
5
5
|
type UrlParams = Record<'baseUrl' | 'fullPath', string>;
|
|
6
6
|
interface ProductInfo {
|
|
7
|
+
/** The id of the product. */
|
|
8
|
+
productId: number;
|
|
7
9
|
/** The name of the product. */
|
|
8
10
|
name: string;
|
|
9
11
|
/** The brand of the product. */
|
|
10
12
|
brand: string;
|
|
11
13
|
/** A description of the product. */
|
|
12
14
|
productDescription: string;
|
|
15
|
+
/** The color of the product. */
|
|
16
|
+
color: string;
|
|
13
17
|
/** Array of product variants. */
|
|
14
|
-
variants:
|
|
18
|
+
variants: WithContext<Product>[];
|
|
15
19
|
/** Array of image URLs for the product. */
|
|
16
20
|
images: string[];
|
|
21
|
+
variesBy?: ProductGroup['variesBy'];
|
|
17
22
|
}
|
|
18
23
|
export interface UseProductSeoDataReturn {
|
|
19
24
|
/** The title of the product. */
|
|
@@ -22,8 +27,8 @@ export interface UseProductSeoDataReturn {
|
|
|
22
27
|
robots: string;
|
|
23
28
|
/** Computed ref for canonical link tag metadata. */
|
|
24
29
|
canonicalLink: ComputedRef<CanonicalLink[]>;
|
|
25
|
-
/** Computed ref containing JSON-LD structured data for the product. */
|
|
26
|
-
productJsonLd: ComputedRef<WithContext<
|
|
30
|
+
/** Computed ref containing JSON-LD structured data for the product group. */
|
|
31
|
+
productJsonLd: ComputedRef<WithContext<ProductGroup>>;
|
|
27
32
|
/** Computed ref containing JSON-LD structured data for product breadcrumbs. */
|
|
28
33
|
productBreadcrumbJsonLd: ComputedRef<WithContext<BreadcrumbList>>;
|
|
29
34
|
}
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
generateCategoryBreadcrumbSchema,
|
|
4
4
|
sanitizeCanonicalURL
|
|
5
5
|
} from "@scayle/storefront-nuxt";
|
|
6
|
-
import {
|
|
6
|
+
import { generateProductGroupSchema } from "../utils/seo.js";
|
|
7
7
|
export function useProductSeoData(breadcrumbs, urlParams, productInfo) {
|
|
8
8
|
const canonicalUrl = computed(() => {
|
|
9
9
|
const url = `${toValue(urlParams).baseUrl}${toValue(urlParams).fullPath}`;
|
|
@@ -14,13 +14,16 @@ export function useProductSeoData(breadcrumbs, urlParams, productInfo) {
|
|
|
14
14
|
});
|
|
15
15
|
const robots = "index, follow";
|
|
16
16
|
const productJsonLd = computed(() => {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
images:
|
|
23
|
-
|
|
17
|
+
const info = toValue(productInfo);
|
|
18
|
+
return generateProductGroupSchema({
|
|
19
|
+
productId: info.productId.toString(),
|
|
20
|
+
name: info.name,
|
|
21
|
+
description: info.productDescription,
|
|
22
|
+
images: info.images,
|
|
23
|
+
brandName: info.brand,
|
|
24
|
+
color: info.color,
|
|
25
|
+
variants: info.variants,
|
|
26
|
+
variesBy: info.variesBy
|
|
24
27
|
});
|
|
25
28
|
});
|
|
26
29
|
const productBreadcrumbJsonLd = computed(() => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Variant } from '@scayle/storefront-nuxt';
|
|
2
|
-
import type { Product, WithContext } from 'schema-dts';
|
|
2
|
+
import type { Product, WithContext, ProductGroup } from 'schema-dts';
|
|
3
3
|
/**
|
|
4
4
|
* Generates an object following the schema.org `Product` type with context.
|
|
5
5
|
*
|
|
@@ -8,11 +8,19 @@ import type { Product, WithContext } from 'schema-dts';
|
|
|
8
8
|
*
|
|
9
9
|
* @returns An object following the schema.org `Product` type with context.
|
|
10
10
|
*/
|
|
11
|
-
export declare const generateProductSchema: ({ productName,
|
|
11
|
+
export declare const generateProductSchema: ({ productName, variant, url, size, }: {
|
|
12
12
|
productName: string;
|
|
13
|
-
|
|
14
|
-
variants: Variant[];
|
|
13
|
+
variant: Variant;
|
|
15
14
|
url: string;
|
|
16
|
-
|
|
17
|
-
images?: string[];
|
|
15
|
+
size?: string;
|
|
18
16
|
}) => WithContext<Product>;
|
|
17
|
+
export declare const generateProductGroupSchema: ({ productId, name, description, images, brandName, color, variants, variesBy, }: {
|
|
18
|
+
productId: string;
|
|
19
|
+
name: string;
|
|
20
|
+
description: string;
|
|
21
|
+
images: string[];
|
|
22
|
+
brandName: string;
|
|
23
|
+
color: string;
|
|
24
|
+
variants: Product[];
|
|
25
|
+
variesBy?: ProductGroup["variesBy"];
|
|
26
|
+
}) => WithContext<ProductGroup>;
|
|
@@ -1,38 +1,58 @@
|
|
|
1
1
|
import { isInStock } from "@scayle/storefront-nuxt";
|
|
2
|
-
const
|
|
3
|
-
|
|
2
|
+
const generateSchemaProductOffer = ({
|
|
3
|
+
variant,
|
|
4
|
+
baseUrl
|
|
4
5
|
}) => {
|
|
5
|
-
|
|
6
|
+
const url = new URL(baseUrl);
|
|
7
|
+
url.searchParams.append("variantId", variant.id.toString());
|
|
8
|
+
return {
|
|
6
9
|
"@type": "Offer",
|
|
10
|
+
url: url.href,
|
|
7
11
|
mpn: variant.referenceKey,
|
|
8
12
|
sku: variant.id.toString(),
|
|
9
13
|
price: variant.price.withTax / 100,
|
|
10
14
|
priceCurrency: variant.price.currencyCode,
|
|
11
15
|
availability: isInStock(variant) ? "https://schema.org/InStock" : "https://schema.org/OutOfStock",
|
|
12
16
|
itemCondition: "https://schema.org/NewCondition"
|
|
13
|
-
}
|
|
17
|
+
};
|
|
14
18
|
};
|
|
15
19
|
export const generateProductSchema = ({
|
|
16
20
|
productName,
|
|
17
|
-
|
|
18
|
-
variants,
|
|
21
|
+
variant,
|
|
19
22
|
url,
|
|
20
|
-
|
|
21
|
-
description
|
|
23
|
+
size
|
|
22
24
|
}) => {
|
|
23
|
-
const offers = generateSchemaProductOffers({ variants });
|
|
24
|
-
const brand = {
|
|
25
|
-
"@type": "Brand",
|
|
26
|
-
name: brandName
|
|
27
|
-
};
|
|
28
25
|
return {
|
|
29
26
|
"@context": "https://schema.org",
|
|
30
27
|
"@type": "Product",
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
name: `${productName} (size)`,
|
|
29
|
+
size,
|
|
30
|
+
offers: generateSchemaProductOffer({ variant, baseUrl: url })
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
export const generateProductGroupSchema = ({
|
|
34
|
+
productId,
|
|
35
|
+
name,
|
|
36
|
+
description,
|
|
37
|
+
images,
|
|
38
|
+
brandName,
|
|
39
|
+
color,
|
|
40
|
+
variants,
|
|
41
|
+
variesBy
|
|
42
|
+
}) => {
|
|
43
|
+
return {
|
|
44
|
+
"@context": "https://schema.org",
|
|
45
|
+
"@type": "ProductGroup",
|
|
46
|
+
productID: productId,
|
|
47
|
+
name: `${name}, ${color}`,
|
|
34
48
|
description,
|
|
35
|
-
|
|
36
|
-
|
|
49
|
+
image: images,
|
|
50
|
+
brand: {
|
|
51
|
+
"@type": "Brand",
|
|
52
|
+
name: brandName
|
|
53
|
+
},
|
|
54
|
+
color,
|
|
55
|
+
...variants.length ? { hasVariant: variants } : {},
|
|
56
|
+
...variesBy ? { variesBy } : {}
|
|
37
57
|
};
|
|
38
58
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scayle/storefront-product-detail",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Collection of essential composables and utilities to work with product detail",
|
|
5
5
|
"author": "SCAYLE Commerce Engine",
|
|
6
6
|
"license": "MIT",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"main": "./dist/module.cjs",
|
|
20
20
|
"types": "./dist/types.d.ts",
|
|
21
21
|
"files": [
|
|
22
|
+
"CHANGELOG.md",
|
|
22
23
|
"dist"
|
|
23
24
|
],
|
|
24
25
|
"imports": {
|
|
@@ -35,7 +36,7 @@
|
|
|
35
36
|
"format:fix": "dprint fmt",
|
|
36
37
|
"test": "vitest --run --passWithNoTests",
|
|
37
38
|
"test:watch": "vitest --passWithNoTests",
|
|
38
|
-
"test:ci": "vitest --run --passWithNoTests --coverage --reporter=default --reporter=junit --outputFile=./junit.xml",
|
|
39
|
+
"test:ci": "vitest --run --passWithNoTests --coverage --reporter=default --reporter=junit --outputFile=./coverage/junit.xml",
|
|
39
40
|
"typecheck": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit",
|
|
40
41
|
"package:lint": "publint"
|
|
41
42
|
},
|
|
@@ -43,28 +44,28 @@
|
|
|
43
44
|
"@nuxt/kit": "^3.13.0",
|
|
44
45
|
"@scayle/storefront-nuxt": "^8.0.0",
|
|
45
46
|
"@vue/test-utils": "2.4.6",
|
|
46
|
-
"@vueuse/core": "12.
|
|
47
|
+
"@vueuse/core": "12.7.0",
|
|
47
48
|
"vue-router": "4.5.0"
|
|
48
49
|
},
|
|
49
50
|
"devDependencies": {
|
|
50
51
|
"@nuxt/kit": "3.14.1592",
|
|
51
52
|
"@nuxt/module-builder": "0.8.4",
|
|
52
53
|
"@nuxt/schema": "3.14.1592",
|
|
53
|
-
"@nuxt/test-utils": "3.15.
|
|
54
|
-
"@scayle/eslint-config-storefront": "4.4.
|
|
55
|
-
"@scayle/storefront-nuxt": "8.
|
|
56
|
-
"@types/node": "22.
|
|
54
|
+
"@nuxt/test-utils": "3.15.4",
|
|
55
|
+
"@scayle/eslint-config-storefront": "4.4.1",
|
|
56
|
+
"@scayle/storefront-nuxt": "8.10.3",
|
|
57
|
+
"@types/node": "22.13.4",
|
|
57
58
|
"@vue/test-utils": "2.4.6",
|
|
58
|
-
"@vueuse/core": "12.
|
|
59
|
-
"dprint": "0.
|
|
60
|
-
"eslint": "9.
|
|
61
|
-
"happy-dom": "
|
|
59
|
+
"@vueuse/core": "12.7.0",
|
|
60
|
+
"dprint": "0.49.0",
|
|
61
|
+
"eslint": "9.20.1",
|
|
62
|
+
"happy-dom": "17.1.1",
|
|
62
63
|
"eslint-formatter-gitlab": "5.1.0",
|
|
63
64
|
"nuxt": "3.14.1592",
|
|
64
65
|
"publint": "0.2.12",
|
|
65
|
-
"typescript": "5.
|
|
66
|
-
"vitest": "2.1.
|
|
66
|
+
"typescript": "5.7.3",
|
|
67
|
+
"vitest": "2.1.9",
|
|
67
68
|
"vue-router": "4.5.0",
|
|
68
|
-
"vue-tsc": "2.
|
|
69
|
+
"vue-tsc": "2.2.2"
|
|
69
70
|
}
|
|
70
71
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { useProductSeoData } from "~/src/runtime";
|
|
3
|
-
import { variantFactory } from "@scayle/storefront-nuxt/dist/test/factories";
|
|
4
|
-
import { ref } from "vue";
|
|
5
|
-
describe("useProductSeoData", () => {
|
|
6
|
-
it("should return product seo data from product", () => {
|
|
7
|
-
const {
|
|
8
|
-
title,
|
|
9
|
-
productBreadcrumbJsonLd,
|
|
10
|
-
productJsonLd,
|
|
11
|
-
robots,
|
|
12
|
-
canonicalLink
|
|
13
|
-
} = useProductSeoData(
|
|
14
|
-
[{ value: "value", to: "/" }],
|
|
15
|
-
{ baseUrl: "baseUrl", fullPath: "fullPath" },
|
|
16
|
-
{
|
|
17
|
-
name: "name",
|
|
18
|
-
variants: [variantFactory.build({ id: 1 })],
|
|
19
|
-
brand: "brand",
|
|
20
|
-
productDescription: "description",
|
|
21
|
-
images: []
|
|
22
|
-
}
|
|
23
|
-
);
|
|
24
|
-
expect(title.value).toBe("name");
|
|
25
|
-
expect(productBreadcrumbJsonLd.value).toStrictEqual({
|
|
26
|
-
"@context": "https://schema.org",
|
|
27
|
-
"@type": "BreadcrumbList",
|
|
28
|
-
"itemListElement": [
|
|
29
|
-
{
|
|
30
|
-
"@type": "ListItem",
|
|
31
|
-
"item": "/",
|
|
32
|
-
"name": "value",
|
|
33
|
-
"position": 1
|
|
34
|
-
}
|
|
35
|
-
]
|
|
36
|
-
});
|
|
37
|
-
expect(productJsonLd.value).toStrictEqual({
|
|
38
|
-
"@context": "https://schema.org",
|
|
39
|
-
"@type": "Product",
|
|
40
|
-
"brand": {
|
|
41
|
-
"@type": "Brand",
|
|
42
|
-
"name": "brand"
|
|
43
|
-
},
|
|
44
|
-
"description": "description",
|
|
45
|
-
"image": [],
|
|
46
|
-
"name": "name",
|
|
47
|
-
"offers": [
|
|
48
|
-
{
|
|
49
|
-
"@type": "Offer",
|
|
50
|
-
"availability": "https://schema.org/InStock",
|
|
51
|
-
"itemCondition": "https://schema.org/NewCondition",
|
|
52
|
-
"mpn": void 0,
|
|
53
|
-
"price": 1,
|
|
54
|
-
"priceCurrency": "USD",
|
|
55
|
-
"sku": "1"
|
|
56
|
-
}
|
|
57
|
-
],
|
|
58
|
-
"url": "baseUrlfullPath"
|
|
59
|
-
});
|
|
60
|
-
expect(robots).toBe("index, follow");
|
|
61
|
-
expect(canonicalLink.value).toStrictEqual([
|
|
62
|
-
{
|
|
63
|
-
"href": "baseUrlfullPath",
|
|
64
|
-
"key": "canonical",
|
|
65
|
-
"rel": "canonical"
|
|
66
|
-
}
|
|
67
|
-
]);
|
|
68
|
-
});
|
|
69
|
-
it("should handle reactivity correctly with changing parameters", () => {
|
|
70
|
-
const breadcrubs = [{ value: "value", to: "/" }];
|
|
71
|
-
const urlParams = { baseUrl: "baseUrl", fullPath: "fullPath" };
|
|
72
|
-
const productInfo = ref({
|
|
73
|
-
name: "name",
|
|
74
|
-
variants: [variantFactory.build({ id: 1 })],
|
|
75
|
-
brand: "brand",
|
|
76
|
-
productDescription: "description",
|
|
77
|
-
images: []
|
|
78
|
-
});
|
|
79
|
-
const {
|
|
80
|
-
title,
|
|
81
|
-
productBreadcrumbJsonLd,
|
|
82
|
-
productJsonLd,
|
|
83
|
-
canonicalLink
|
|
84
|
-
} = useProductSeoData(
|
|
85
|
-
breadcrubs,
|
|
86
|
-
urlParams,
|
|
87
|
-
productInfo
|
|
88
|
-
);
|
|
89
|
-
expect(title.value).toBe("name");
|
|
90
|
-
expect(productBreadcrumbJsonLd.value).toStrictEqual({
|
|
91
|
-
"@context": "https://schema.org",
|
|
92
|
-
"@type": "BreadcrumbList",
|
|
93
|
-
"itemListElement": [
|
|
94
|
-
{
|
|
95
|
-
"@type": "ListItem",
|
|
96
|
-
"item": "/",
|
|
97
|
-
"name": "value",
|
|
98
|
-
"position": 1
|
|
99
|
-
}
|
|
100
|
-
]
|
|
101
|
-
});
|
|
102
|
-
expect(productJsonLd.value).toStrictEqual({
|
|
103
|
-
"@context": "https://schema.org",
|
|
104
|
-
"@type": "Product",
|
|
105
|
-
"brand": {
|
|
106
|
-
"@type": "Brand",
|
|
107
|
-
"name": "brand"
|
|
108
|
-
},
|
|
109
|
-
"description": "description",
|
|
110
|
-
"image": [],
|
|
111
|
-
"name": "name",
|
|
112
|
-
"offers": [
|
|
113
|
-
{
|
|
114
|
-
"@type": "Offer",
|
|
115
|
-
"availability": "https://schema.org/InStock",
|
|
116
|
-
"itemCondition": "https://schema.org/NewCondition",
|
|
117
|
-
"mpn": void 0,
|
|
118
|
-
"price": 1,
|
|
119
|
-
"priceCurrency": "USD",
|
|
120
|
-
"sku": "1"
|
|
121
|
-
}
|
|
122
|
-
],
|
|
123
|
-
"url": "baseUrlfullPath"
|
|
124
|
-
});
|
|
125
|
-
expect(canonicalLink.value).toStrictEqual([
|
|
126
|
-
{
|
|
127
|
-
"href": "baseUrlfullPath",
|
|
128
|
-
"key": "canonical",
|
|
129
|
-
"rel": "canonical"
|
|
130
|
-
}
|
|
131
|
-
]);
|
|
132
|
-
breadcrubs[0].to = "/other";
|
|
133
|
-
urlParams.baseUrl = "otherUrl";
|
|
134
|
-
productInfo.value.name = "otherName";
|
|
135
|
-
expect(title.value).toBe("otherName");
|
|
136
|
-
expect(productBreadcrumbJsonLd.value).toStrictEqual({
|
|
137
|
-
"@context": "https://schema.org",
|
|
138
|
-
"@type": "BreadcrumbList",
|
|
139
|
-
"itemListElement": [
|
|
140
|
-
{
|
|
141
|
-
"@type": "ListItem",
|
|
142
|
-
"item": "/other",
|
|
143
|
-
"name": "value",
|
|
144
|
-
"position": 1
|
|
145
|
-
}
|
|
146
|
-
]
|
|
147
|
-
});
|
|
148
|
-
expect(productJsonLd.value).toStrictEqual({
|
|
149
|
-
"@context": "https://schema.org",
|
|
150
|
-
"@type": "Product",
|
|
151
|
-
"brand": {
|
|
152
|
-
"@type": "Brand",
|
|
153
|
-
"name": "brand"
|
|
154
|
-
},
|
|
155
|
-
"description": "description",
|
|
156
|
-
"image": [],
|
|
157
|
-
"name": "otherName",
|
|
158
|
-
"offers": [
|
|
159
|
-
{
|
|
160
|
-
"@type": "Offer",
|
|
161
|
-
"availability": "https://schema.org/InStock",
|
|
162
|
-
"itemCondition": "https://schema.org/NewCondition",
|
|
163
|
-
"mpn": void 0,
|
|
164
|
-
"price": 1,
|
|
165
|
-
"priceCurrency": "USD",
|
|
166
|
-
"sku": "1"
|
|
167
|
-
}
|
|
168
|
-
],
|
|
169
|
-
"url": "otherUrlfullPath"
|
|
170
|
-
});
|
|
171
|
-
expect(canonicalLink.value).toStrictEqual([
|
|
172
|
-
{
|
|
173
|
-
"href": "otherUrlfullPath",
|
|
174
|
-
"key": "canonical",
|
|
175
|
-
"rel": "canonical"
|
|
176
|
-
}
|
|
177
|
-
]);
|
|
178
|
-
});
|
|
179
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { it, describe, expect } from "vitest";
|
|
2
|
-
import { attributeGroupFactory } from "@scayle/storefront-nuxt/dist/test/factories";
|
|
3
|
-
import { getFilteredAttributeGroups } from "./attribute.js";
|
|
4
|
-
describe("attribute", () => {
|
|
5
|
-
describe("getFilteredAttributeGroups", () => {
|
|
6
|
-
it("should return correct filtered and formatted attributes", () => {
|
|
7
|
-
const attributes = {
|
|
8
|
-
design: attributeGroupFactory.build({
|
|
9
|
-
type: "design",
|
|
10
|
-
label: "Jackenart",
|
|
11
|
-
values: { id: 2534, label: "Bomberjacke", value: "bomberjacke" }
|
|
12
|
-
}),
|
|
13
|
-
extras: attributeGroupFactory.build({
|
|
14
|
-
label: "Extras",
|
|
15
|
-
type: "extras",
|
|
16
|
-
multiSelect: true,
|
|
17
|
-
values: [
|
|
18
|
-
{ id: 2427, label: "Weicher Griff", value: "weicher_griff" },
|
|
19
|
-
{ id: 2435, label: "Ton-in-Ton-N\xE4hte", value: "tonintonnhte" },
|
|
20
|
-
{ id: 2438, label: "Label-Stickerei", value: "labelstickerei" }
|
|
21
|
-
]
|
|
22
|
-
})
|
|
23
|
-
};
|
|
24
|
-
const formattedAttributes = getFilteredAttributeGroups(
|
|
25
|
-
attributes,
|
|
26
|
-
["design", "extras"]
|
|
27
|
-
);
|
|
28
|
-
expect(formattedAttributes).toHaveLength(2);
|
|
29
|
-
expect(formattedAttributes.get("design")).toStrictEqual([
|
|
30
|
-
"Jackenart: Bomberjacke"
|
|
31
|
-
]);
|
|
32
|
-
expect(formattedAttributes.get("extras")).toStrictEqual([
|
|
33
|
-
"Weicher Griff",
|
|
34
|
-
"Ton-in-Ton-N\xE4hte",
|
|
35
|
-
"Label-Stickerei"
|
|
36
|
-
]);
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { it, describe } from "vitest";
|
|
2
|
-
import { getCombineWithProductIds } from "~/src/runtime/utils/product";
|
|
3
|
-
import { advancedAttributeFactory } from "@scayle/storefront-nuxt/dist/test/factories";
|
|
4
|
-
describe("getCombineWithProductIds", () => {
|
|
5
|
-
it("should return productIds from data", ({ expect }) => {
|
|
6
|
-
const combineWithAttribute = advancedAttributeFactory.build({
|
|
7
|
-
key: "combineWith",
|
|
8
|
-
label: "Kombiniere mit",
|
|
9
|
-
values: [
|
|
10
|
-
{
|
|
11
|
-
fieldSet: [
|
|
12
|
-
[
|
|
13
|
-
{
|
|
14
|
-
value: "206131"
|
|
15
|
-
}
|
|
16
|
-
]
|
|
17
|
-
],
|
|
18
|
-
groupSet: []
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
fieldSet: [
|
|
22
|
-
[
|
|
23
|
-
{
|
|
24
|
-
value: "206017"
|
|
25
|
-
}
|
|
26
|
-
]
|
|
27
|
-
],
|
|
28
|
-
groupSet: []
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
fieldSet: [
|
|
32
|
-
[
|
|
33
|
-
{
|
|
34
|
-
value: "206012"
|
|
35
|
-
}
|
|
36
|
-
]
|
|
37
|
-
],
|
|
38
|
-
groupSet: []
|
|
39
|
-
}
|
|
40
|
-
]
|
|
41
|
-
});
|
|
42
|
-
expect(getCombineWithProductIds(combineWithAttribute)).toStrictEqual([
|
|
43
|
-
206131,
|
|
44
|
-
206017,
|
|
45
|
-
206012
|
|
46
|
-
]);
|
|
47
|
-
});
|
|
48
|
-
it("should return empty array when attribute is not available", ({ expect }) => {
|
|
49
|
-
expect(getCombineWithProductIds(void 0)).toStrictEqual([]);
|
|
50
|
-
});
|
|
51
|
-
it("should return productIds ignore invalid data", ({ expect }) => {
|
|
52
|
-
const combineWithAttributeWithInvalidData = advancedAttributeFactory.build({
|
|
53
|
-
key: "combineWith",
|
|
54
|
-
label: "Kombiniere mit",
|
|
55
|
-
values: [
|
|
56
|
-
{
|
|
57
|
-
fieldSet: [
|
|
58
|
-
[
|
|
59
|
-
{
|
|
60
|
-
value: "206131"
|
|
61
|
-
}
|
|
62
|
-
],
|
|
63
|
-
[
|
|
64
|
-
{
|
|
65
|
-
value: "TEST"
|
|
66
|
-
}
|
|
67
|
-
]
|
|
68
|
-
],
|
|
69
|
-
groupSet: []
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
fieldSet: [
|
|
73
|
-
[
|
|
74
|
-
{
|
|
75
|
-
value: "206017"
|
|
76
|
-
}
|
|
77
|
-
]
|
|
78
|
-
],
|
|
79
|
-
groupSet: []
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
fieldSet: [
|
|
83
|
-
[
|
|
84
|
-
{
|
|
85
|
-
value: "206012"
|
|
86
|
-
}
|
|
87
|
-
]
|
|
88
|
-
],
|
|
89
|
-
groupSet: []
|
|
90
|
-
}
|
|
91
|
-
]
|
|
92
|
-
});
|
|
93
|
-
expect(
|
|
94
|
-
getCombineWithProductIds(combineWithAttributeWithInvalidData)
|
|
95
|
-
).toStrictEqual([206131, 206017, 206012]);
|
|
96
|
-
});
|
|
97
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { generateProductSchema } from "~/src/runtime/utils/seo";
|
|
3
|
-
import { variantFactory } from "@scayle/storefront-nuxt/dist/test/factories";
|
|
4
|
-
describe("generateProductSchema", () => {
|
|
5
|
-
it("should return productSchema from product", () => {
|
|
6
|
-
const productSchema = generateProductSchema({
|
|
7
|
-
productName: "productName",
|
|
8
|
-
brandName: "brandName",
|
|
9
|
-
variants: [variantFactory.build({ id: 1 })],
|
|
10
|
-
url: "url",
|
|
11
|
-
images: [],
|
|
12
|
-
description: "description"
|
|
13
|
-
});
|
|
14
|
-
expect(productSchema).toStrictEqual({
|
|
15
|
-
"@context": "https://schema.org",
|
|
16
|
-
"@type": "Product",
|
|
17
|
-
"brand": {
|
|
18
|
-
"@type": "Brand",
|
|
19
|
-
"name": "brandName"
|
|
20
|
-
},
|
|
21
|
-
"description": "description",
|
|
22
|
-
"image": [],
|
|
23
|
-
"name": "productName",
|
|
24
|
-
"offers": [
|
|
25
|
-
{
|
|
26
|
-
"@type": "Offer",
|
|
27
|
-
"availability": "https://schema.org/InStock",
|
|
28
|
-
"itemCondition": "https://schema.org/NewCondition",
|
|
29
|
-
"mpn": void 0,
|
|
30
|
-
"price": 1,
|
|
31
|
-
"priceCurrency": "USD",
|
|
32
|
-
"sku": "1"
|
|
33
|
-
}
|
|
34
|
-
],
|
|
35
|
-
"url": "url"
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
});
|