Skip to content

Commit 3f79d8d

Browse files
committed
fix: support priceValidUntil in simple products
1 parent cebc0bc commit 3f79d8d

File tree

6 files changed

+70
-11
lines changed

6 files changed

+70
-11
lines changed

src/content/queries/cs-product.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
*/
1212

1313
import { forceImagesHTTPS } from '../../utils/http.js';
14-
import { gql } from '../../utils/product.js';
14+
import { gql, parseSpecialToDate } from '../../utils/product.js';
1515

1616
/**
1717
* @param {any} productData
@@ -90,6 +90,11 @@ export const adapter = (productData) => {
9090
} : null,
9191
};
9292

93+
const specialToDate = parseSpecialToDate(product);
94+
if (specialToDate) {
95+
product.specialToDate = specialToDate;
96+
}
97+
9398
return product;
9499
};
95100

src/content/queries/cs-variants.js

+3-8
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
*/
1212

1313
import { forceImagesHTTPS } from '../../utils/http.js';
14-
import { gql } from '../../utils/product.js';
14+
import { gql, parseSpecialToDate } from '../../utils/product.js';
1515

1616
/**
1717
* @param {any} variants
@@ -50,14 +50,9 @@ export const adapter = (config, variants) => variants.map(({ selections, product
5050
selections: selections ?? [],
5151
};
5252

53-
const specialToDate = product.attributes?.find((attr) => attr.name === 'special_to_date')?.value;
53+
const specialToDate = parseSpecialToDate(product);
5454
if (specialToDate) {
55-
const today = new Date();
56-
const specialPriceToDate = new Date(specialToDate);
57-
if (specialPriceToDate.getTime() >= today.getTime()) {
58-
const [date] = specialToDate.split(' ');
59-
variant.specialToDate = date;
60-
}
55+
variant.specialToDate = specialToDate;
6156
}
6257

6358
if (config.attributeOverrides?.variant) {

src/templates/html/HTMLTemplate.js

+8
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,10 @@ ${HTMLTemplate.metaProperty('product:price.currency', product.prices.final.curre
272272
* @returns {string}
273273
*/
274274
renderProductVariants() {
275+
if (!this.variants) {
276+
return '';
277+
}
278+
275279
return /* html */ `\
276280
<div class="product-variants">
277281
${this.variants.map((v) => /* html */`\
@@ -292,6 +296,10 @@ ${HTMLTemplate.metaProperty('product:price.currency', product.prices.final.curre
292296
* @returns {string}
293297
*/
294298
renderProductVariantsAttributes() {
299+
if (!this.variants) {
300+
return '';
301+
}
302+
295303
return /* html */ `\
296304
<div class="variant-attributes">
297305
${this.variants?.map((v) => /* html */`\

src/types.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ declare global {
9090
urlKey?: string;
9191
externalId?: string;
9292
variants?: Variant[]; // variants exist on products in helix commerce but not on magento
93+
specialToDate?: string;
9394

9495
// not handled currently:
9596
externalParentId?: string;

src/utils/product.js

+13
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,16 @@ export function matchConfigPath(config, path) {
9595
console.warn('No match found for path:', path);
9696
return null;
9797
}
98+
99+
export function parseSpecialToDate(product) {
100+
const specialToDate = product.attributes?.find((attr) => attr.name === 'special_to_date')?.value;
101+
if (specialToDate) {
102+
const today = new Date();
103+
const specialPriceToDate = new Date(specialToDate);
104+
if (specialPriceToDate.getTime() >= today.getTime()) {
105+
const [date] = specialToDate.split(' ');
106+
return date;
107+
}
108+
}
109+
return undefined;
110+
}

test/templates/html/index.test.js

+39-2
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,9 @@ describe('Render Product HTML', () => {
7777
assert.strictEqual(twitterDescription.getAttribute('content'), expectedDescription, 'Twitter description does not match expected value');
7878
});
7979

80-
it('should have the correct JSON-LD schema', () => {
80+
it('should have the correct JSON-LD schema with variants', () => {
8181
const jsonLdScript = document.querySelector('script[type="application/ld+json"]');
8282
assert.ok(jsonLdScript, 'JSON-LD script tag should exist');
83-
8483
// @ts-ignore
8584
const productTemplate = new JSONTemplate(DEFAULT_CONTEXT({ config }), product, variations);
8685

@@ -108,6 +107,44 @@ describe('Render Product HTML', () => {
108107
});
109108
});
110109

110+
it('should have the correct JSON-LD schema without variants (simple product)', () => {
111+
variations = undefined;
112+
const html = htmlTemplateFromContext(DEFAULT_CONTEXT({ config }), product, variations).render();
113+
dom = new JSDOM(html);
114+
document = dom.window.document;
115+
116+
const jsonLdScript = document.querySelector('script[type="application/ld+json"]');
117+
118+
product.attributes.push({
119+
name: 'special_to_date',
120+
value: '2024-12-31',
121+
});
122+
// @ts-ignore
123+
const productTemplate = new JSONTemplate(DEFAULT_CONTEXT({ config }), product, undefined);
124+
125+
const jsonLd = JSON.parse(jsonLdScript.textContent);
126+
assert.strictEqual(jsonLd['@type'], 'Product', 'JSON-LD @type should be Product');
127+
assert.strictEqual(jsonLd['@id'], productTemplate.constructProductURL(), 'JSON-LD @id does not match product URL');
128+
assert.strictEqual(jsonLd.name, product.name, 'JSON-LD name does not match product name');
129+
assert.strictEqual(jsonLd.sku, product.sku, 'JSON-LD SKU does not match product SKU');
130+
assert.strictEqual(jsonLd.description, product.metaDescription, 'JSON-LD description does not match product description');
131+
assert.strictEqual(jsonLd.image, product.images[0]?.url || '', 'JSON-LD image does not match product image');
132+
assert.strictEqual(jsonLd.productID, product.sku, 'JSON-LD productID does not match product SKU');
133+
assert.ok(Array.isArray(jsonLd.offers), 'JSON-LD offers should be an array');
134+
assert.strictEqual(jsonLd.offers.length, 1, 'JSON-LD offers length does not match number of variants');
135+
jsonLd.offers.forEach((offer) => {
136+
assert.strictEqual(offer['@type'], 'Offer', `Offer type for variant ${product.sku} should be Offer`);
137+
assert.strictEqual(offer.sku, product.sku, `Offer SKU for variant ${product.sku} does not match`);
138+
assert.strictEqual(offer.url, productTemplate.constructProductURL(), 'JSON-LD offer URL does not match');
139+
assert.strictEqual(offer.price, product.prices.final.amount, `Offer price for variant ${product.sku} does not match`);
140+
assert.strictEqual(offer.priceCurrency, product.prices.final.currency, `Offer priceCurrency for variant ${product.sku} does not match`);
141+
assert.strictEqual(offer.availability, product.inStock ? 'InStock' : 'OutOfStock', `Offer availability for variant ${product.sku} does not match`);
142+
assert.strictEqual(offer.image, product.images[0].url || '', `Offer image for variant ${product.sku} does not match`);
143+
assert.strictEqual(offer.priceSpecification, undefined, 'Offer contains priceSpecification for variant when it should not');
144+
assert.strictEqual(offer.priceValidUntil, product.specialToDate, 'Offer does not contain priceValidUntil for variant');
145+
});
146+
});
147+
111148
it('should have the correct JSON-LD schema with attribute overrides', () => {
112149
variations = [
113150
createProductVariationFixture({ gtin: '123' }),

0 commit comments

Comments
 (0)