From dfa868f43c40a6c6d06965224f0e2eab3cafabb0 Mon Sep 17 00:00:00 2001 From: Sriram Date: Wed, 7 Jan 2026 12:57:42 +0530 Subject: [PATCH 1/3] feat: add fieldName property to validation error objects - Add fieldName to required(), recommended(), or() in base.js - Add fieldName to custom validators (BreadcrumbList, DefinedRegion, MerchantReturnPolicy, Product, ProductMerchant, Rating, schemaOrg) - Add tests for fieldName in base.test.js Fixes #55 --- package-lock.json | 6 ++ src/types/BreadcrumbList.js | 2 + src/types/DefinedRegion.js | 2 + src/types/MerchantReturnPolicy.js | 6 ++ src/types/Product.js | 4 + src/types/ProductMerchant.js | 2 + src/types/Rating.js | 1 + src/types/__tests__/base.test.js | 141 ++++++++++++++++++++++++++++++ src/types/base.js | 12 +++ src/types/schemaOrg.js | 1 + 10 files changed, 177 insertions(+) create mode 100644 src/types/__tests__/base.test.js diff --git a/package-lock.json b/package-lock.json index fd1a253..a916e4e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -415,6 +415,7 @@ "integrity": "sha512-ODsoD39Lq6vR6aBgvjTnA3nZGliknKboc9Gtxr7E4WDNqY24MxANKcuDQSF0jzapvGb3KWOEDrKfve4HoWGK+g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.1", @@ -1179,6 +1180,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2343,6 +2345,7 @@ "integrity": "sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -3664,6 +3667,7 @@ "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", "dev": true, "license": "MIT", + "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -6416,6 +6420,7 @@ "dev": true, "inBundle": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -7339,6 +7344,7 @@ "integrity": "sha512-g7RssbTAbir1k/S7uSwSVZFfFXwpomUB9Oas0+xi9KStSCmeDXcA7rNhiskjLqvUe/Evhx8fVCT16OSa34eM5g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@semantic-release/commit-analyzer": "^13.0.0-beta.1", "@semantic-release/error": "^4.0.0", diff --git a/src/types/BreadcrumbList.js b/src/types/BreadcrumbList.js index 259029d..63d7a1c 100644 --- a/src/types/BreadcrumbList.js +++ b/src/types/BreadcrumbList.js @@ -32,6 +32,7 @@ export default class BreadcrumbListValidator extends BaseValidator { issueMessage: 'At least two ListItems are required', severity: 'WARNING', path: this.path, + fieldName: 'itemListElement', }; } return null; @@ -137,6 +138,7 @@ export default class BreadcrumbListValidator extends BaseValidator { issueMessage: e, severity: 'WARNING', path: newPath, + fieldName: urlPath || 'item', }); } } diff --git a/src/types/DefinedRegion.js b/src/types/DefinedRegion.js index 8fe64f9..0d0facf 100644 --- a/src/types/DefinedRegion.js +++ b/src/types/DefinedRegion.js @@ -30,6 +30,8 @@ export default class DefinedRegionValidator extends BaseValidator { issueMessage: 'Only one of addressRegion or postalCode can be used', severity: 'WARNING', path: this.path, + fieldName: 'addressRegion', + fieldNames: ['addressRegion', 'postalCode'], }; } } diff --git a/src/types/MerchantReturnPolicy.js b/src/types/MerchantReturnPolicy.js index fcd4541..895ae2a 100644 --- a/src/types/MerchantReturnPolicy.js +++ b/src/types/MerchantReturnPolicy.js @@ -84,6 +84,12 @@ export default class MerchantReturnPolicyValidator extends BaseValidator { 'Either applicableCountry and returnPolicyCategory or merchantReturnLink must be present', severity: 'ERROR', path: this.path, + fieldName: 'applicableCountry', + fieldNames: [ + 'applicableCountry', + 'returnPolicyCategory', + 'merchantReturnLink', + ], }; } } diff --git a/src/types/Product.js b/src/types/Product.js index 5b6d990..830fe69 100644 --- a/src/types/Product.js +++ b/src/types/Product.js @@ -47,6 +47,8 @@ export default class ProductValidator extends BaseValidator { 'At least 2 notes, either positive or negative, are required', severity: 'WARNING', path: this.path, + fieldName: 'review', + fieldNames: ['review.positiveNotes', 'review.negativeNotes'], }); } @@ -63,6 +65,8 @@ export default class ProductValidator extends BaseValidator { 'One of the following attributes is required: "aggregateRating", "offers" or "review"', severity: 'ERROR', path: this.path, + fieldName: 'aggregateRating', + fieldNames: ['aggregateRating', 'offers', 'review'], }); } diff --git a/src/types/ProductMerchant.js b/src/types/ProductMerchant.js index abfd632..96d2a8f 100644 --- a/src/types/ProductMerchant.js +++ b/src/types/ProductMerchant.js @@ -81,6 +81,8 @@ export default class ProductMerchantValidator extends BaseValidator { issueMessage: `Missing one of field ${gtinFields.map((a) => `"${a}"`).join(', ')} on either product or all offers`, severity: 'WARNING', path: this.path, + fieldName: 'gtin', + fieldNames: gtinFields, }; } } diff --git a/src/types/Rating.js b/src/types/Rating.js index b88bfc0..282434e 100644 --- a/src/types/Rating.js +++ b/src/types/Rating.js @@ -46,6 +46,7 @@ export default class RatingValidator extends BaseValidator { issueMessage: `Rating is outside the specified or default range`, severity: 'ERROR', path: this.path, + fieldName: 'ratingValue', }; } } diff --git a/src/types/__tests__/base.test.js b/src/types/__tests__/base.test.js new file mode 100644 index 0000000..3c257c3 --- /dev/null +++ b/src/types/__tests__/base.test.js @@ -0,0 +1,141 @@ +/** + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +import { expect } from "chai"; +import BaseValidator from "../base.js"; + +describe("BaseValidator", () => { + describe("fieldName property", () => { + let validator; + const testPath = [{ type: "TestType" }]; + + beforeEach(() => { + validator = new BaseValidator({ + dataFormat: "jsonld", + path: testPath, + }); + }); + + describe("required()", () => { + it("should include fieldName when required attribute is missing", () => { + const condition = validator.required("price"); + const result = condition({}); + + expect(result).to.deep.include({ + severity: "ERROR", + issueMessage: 'Required attribute "price" is missing', + fieldName: "price", + }); + expect(result.path).to.deep.equal(testPath); + }); + + it("should include fieldName when required attribute has invalid type", () => { + const condition = validator.required("price", "number"); + const result = condition({ price: "not-a-number" }); + + expect(result).to.deep.include({ + severity: "ERROR", + issueMessage: 'Invalid type for attribute "price"', + fieldName: "price", + }); + }); + + it("should return null when required attribute is valid", () => { + const condition = validator.required("name"); + const result = condition({ name: "Test Product" }); + + expect(result).to.be.null; + }); + }); + + describe("recommended()", () => { + it("should include fieldName when recommended attribute is missing", () => { + const condition = validator.recommended("description"); + const result = condition({}); + + expect(result).to.deep.include({ + severity: "WARNING", + issueMessage: 'Missing field "description" (optional)', + fieldName: "description", + }); + expect(result.path).to.deep.equal(testPath); + }); + + it("should include fieldName when recommended attribute has invalid type", () => { + const condition = validator.recommended("image", "url"); + const result = condition({ image: "data:invalid" }); + + expect(result).to.deep.include({ + severity: "WARNING", + issueMessage: 'Invalid type for attribute "image"', + fieldName: "image", + }); + }); + + it("should return null when recommended attribute is valid", () => { + const condition = validator.recommended("description"); + const result = condition({ description: "A great product" }); + + expect(result).to.be.null; + }); + }); + + describe("or()", () => { + it("should include fieldName and fieldNames when or conditions fail", () => { + const condition = validator.or( + validator.required("price"), + validator.required("priceSpecification.price") + ); + const result = condition({}); + + expect(result.severity).to.equal("ERROR"); + expect(result.fieldName).to.equal("price"); + expect(result.fieldNames).to.deep.equal([ + "price", + "priceSpecification.price", + ]); + expect(result.path).to.deep.equal(testPath); + }); + + it("should return null when at least one or condition passes", () => { + const condition = validator.or( + validator.required("price"), + validator.required("priceSpecification.price") + ); + const result = condition({ price: "19.99" }); + + expect(result).to.be.null; + }); + + it("should handle single failing condition in or()", () => { + const condition = validator.or( + validator.required("availability") + ); + const result = condition({}); + + expect(result.fieldName).to.equal("availability"); + expect(result.fieldNames).to.deep.equal(["availability"]); + }); + }); + + describe("nested path fieldName", () => { + it("should include fieldName for nested attributes", () => { + const condition = validator.required("offers.price"); + const result = condition({ offers: {} }); + + expect(result).to.deep.include({ + severity: "ERROR", + fieldName: "offers.price", + }); + }); + }); + }); +}); diff --git a/src/types/base.js b/src/types/base.js index 4804a25..abaa6f3 100644 --- a/src/types/base.js +++ b/src/types/base.js @@ -58,6 +58,7 @@ export default class BaseValidator { issueMessage: `Required attribute "${name}" is missing`, severity: 'ERROR', path: this.path, + fieldName: name, }; } if (type && !this.checkType(value, type, ...opts)) { @@ -65,6 +66,7 @@ export default class BaseValidator { issueMessage: `Invalid type for attribute "${name}"`, severity: 'ERROR', path: this.path, + fieldName: name, }; } return null; @@ -89,6 +91,12 @@ export default class BaseValidator { return max; }, 'WARNING'); + // Collect all field names from the conditions + const fieldNames = issues + .flat() + .filter((i) => i && i.fieldName) + .map((i) => i.fieldName); + return { issueMessage: `One of the following conditions needs to be met: ${issues .flat() @@ -96,6 +104,8 @@ export default class BaseValidator { .join(' or ')}`, severity, path: this.path, + fieldName: fieldNames[0] || null, + fieldNames: fieldNames.length > 0 ? fieldNames : undefined, }; }; } @@ -108,6 +118,7 @@ export default class BaseValidator { issueMessage: `Missing field "${name}" (optional)`, severity: 'WARNING', path: this.path, + fieldName: name, }; } if (type && !this.checkType(value, type, ...opts)) { @@ -115,6 +126,7 @@ export default class BaseValidator { issueMessage: `Invalid type for attribute "${name}"`, severity: 'WARNING', path: this.path, + fieldName: name, }; } return null; diff --git a/src/types/schemaOrg.js b/src/types/schemaOrg.js index 88579b7..280b64b 100644 --- a/src/types/schemaOrg.js +++ b/src/types/schemaOrg.js @@ -215,6 +215,7 @@ export default class SchemaOrgValidator { severity: 'WARNING', path: this.path, errorType: 'schemaOrg', + fieldName: propertyId, }); } }), From bfc3239b07939f2b21034e75671c4ba3bdb037c1 Mon Sep 17 00:00:00 2001 From: Sriram Date: Mon, 2 Feb 2026 08:40:04 +0530 Subject: [PATCH 2/3] refactor: consolidate fieldName to fieldNames array Replace fieldName (singular) with fieldNames (array) for consistency. All validation errors now return fieldNames as an array, eliminating redundancy and providing a cleaner API. - Single field errors: fieldNames: ['price'] - Multi-field errors: fieldNames: ['aggregateRating', 'offers', 'review'] Access primary field with error.fieldNames[0] or iterate over all fields. Co-authored-by: Cursor --- src/types/BreadcrumbList.js | 4 ++-- src/types/DefinedRegion.js | 1 - src/types/MerchantReturnPolicy.js | 1 - src/types/Product.js | 2 -- src/types/ProductMerchant.js | 1 - src/types/Rating.js | 2 +- src/types/__tests__/base.test.js | 28 +++++++++++++--------------- src/types/base.js | 15 +++++++-------- src/types/schemaOrg.js | 2 +- 9 files changed, 24 insertions(+), 32 deletions(-) diff --git a/src/types/BreadcrumbList.js b/src/types/BreadcrumbList.js index 63d7a1c..8cb2fc8 100644 --- a/src/types/BreadcrumbList.js +++ b/src/types/BreadcrumbList.js @@ -32,7 +32,7 @@ export default class BreadcrumbListValidator extends BaseValidator { issueMessage: 'At least two ListItems are required', severity: 'WARNING', path: this.path, - fieldName: 'itemListElement', + fieldNames: ['itemListElement'], }; } return null; @@ -138,7 +138,7 @@ export default class BreadcrumbListValidator extends BaseValidator { issueMessage: e, severity: 'WARNING', path: newPath, - fieldName: urlPath || 'item', + fieldNames: [urlPath || 'item'], }); } } diff --git a/src/types/DefinedRegion.js b/src/types/DefinedRegion.js index 0d0facf..abdf079 100644 --- a/src/types/DefinedRegion.js +++ b/src/types/DefinedRegion.js @@ -30,7 +30,6 @@ export default class DefinedRegionValidator extends BaseValidator { issueMessage: 'Only one of addressRegion or postalCode can be used', severity: 'WARNING', path: this.path, - fieldName: 'addressRegion', fieldNames: ['addressRegion', 'postalCode'], }; } diff --git a/src/types/MerchantReturnPolicy.js b/src/types/MerchantReturnPolicy.js index 895ae2a..e8df9cf 100644 --- a/src/types/MerchantReturnPolicy.js +++ b/src/types/MerchantReturnPolicy.js @@ -84,7 +84,6 @@ export default class MerchantReturnPolicyValidator extends BaseValidator { 'Either applicableCountry and returnPolicyCategory or merchantReturnLink must be present', severity: 'ERROR', path: this.path, - fieldName: 'applicableCountry', fieldNames: [ 'applicableCountry', 'returnPolicyCategory', diff --git a/src/types/Product.js b/src/types/Product.js index 830fe69..e21a434 100644 --- a/src/types/Product.js +++ b/src/types/Product.js @@ -47,7 +47,6 @@ export default class ProductValidator extends BaseValidator { 'At least 2 notes, either positive or negative, are required', severity: 'WARNING', path: this.path, - fieldName: 'review', fieldNames: ['review.positiveNotes', 'review.negativeNotes'], }); } @@ -65,7 +64,6 @@ export default class ProductValidator extends BaseValidator { 'One of the following attributes is required: "aggregateRating", "offers" or "review"', severity: 'ERROR', path: this.path, - fieldName: 'aggregateRating', fieldNames: ['aggregateRating', 'offers', 'review'], }); } diff --git a/src/types/ProductMerchant.js b/src/types/ProductMerchant.js index 96d2a8f..412ff21 100644 --- a/src/types/ProductMerchant.js +++ b/src/types/ProductMerchant.js @@ -81,7 +81,6 @@ export default class ProductMerchantValidator extends BaseValidator { issueMessage: `Missing one of field ${gtinFields.map((a) => `"${a}"`).join(', ')} on either product or all offers`, severity: 'WARNING', path: this.path, - fieldName: 'gtin', fieldNames: gtinFields, }; } diff --git a/src/types/Rating.js b/src/types/Rating.js index 282434e..1e78892 100644 --- a/src/types/Rating.js +++ b/src/types/Rating.js @@ -46,7 +46,7 @@ export default class RatingValidator extends BaseValidator { issueMessage: `Rating is outside the specified or default range`, severity: 'ERROR', path: this.path, - fieldName: 'ratingValue', + fieldNames: ['ratingValue'], }; } } diff --git a/src/types/__tests__/base.test.js b/src/types/__tests__/base.test.js index 3c257c3..74f1f9f 100644 --- a/src/types/__tests__/base.test.js +++ b/src/types/__tests__/base.test.js @@ -13,7 +13,7 @@ import { expect } from "chai"; import BaseValidator from "../base.js"; describe("BaseValidator", () => { - describe("fieldName property", () => { + describe("fieldNames property", () => { let validator; const testPath = [{ type: "TestType" }]; @@ -25,27 +25,27 @@ describe("BaseValidator", () => { }); describe("required()", () => { - it("should include fieldName when required attribute is missing", () => { + it("should include fieldNames when required attribute is missing", () => { const condition = validator.required("price"); const result = condition({}); expect(result).to.deep.include({ severity: "ERROR", issueMessage: 'Required attribute "price" is missing', - fieldName: "price", }); + expect(result.fieldNames).to.deep.equal(["price"]); expect(result.path).to.deep.equal(testPath); }); - it("should include fieldName when required attribute has invalid type", () => { + it("should include fieldNames when required attribute has invalid type", () => { const condition = validator.required("price", "number"); const result = condition({ price: "not-a-number" }); expect(result).to.deep.include({ severity: "ERROR", issueMessage: 'Invalid type for attribute "price"', - fieldName: "price", }); + expect(result.fieldNames).to.deep.equal(["price"]); }); it("should return null when required attribute is valid", () => { @@ -57,27 +57,27 @@ describe("BaseValidator", () => { }); describe("recommended()", () => { - it("should include fieldName when recommended attribute is missing", () => { + it("should include fieldNames when recommended attribute is missing", () => { const condition = validator.recommended("description"); const result = condition({}); expect(result).to.deep.include({ severity: "WARNING", issueMessage: 'Missing field "description" (optional)', - fieldName: "description", }); + expect(result.fieldNames).to.deep.equal(["description"]); expect(result.path).to.deep.equal(testPath); }); - it("should include fieldName when recommended attribute has invalid type", () => { + it("should include fieldNames when recommended attribute has invalid type", () => { const condition = validator.recommended("image", "url"); const result = condition({ image: "data:invalid" }); expect(result).to.deep.include({ severity: "WARNING", issueMessage: 'Invalid type for attribute "image"', - fieldName: "image", }); + expect(result.fieldNames).to.deep.equal(["image"]); }); it("should return null when recommended attribute is valid", () => { @@ -89,7 +89,7 @@ describe("BaseValidator", () => { }); describe("or()", () => { - it("should include fieldName and fieldNames when or conditions fail", () => { + it("should include fieldNames when or conditions fail", () => { const condition = validator.or( validator.required("price"), validator.required("priceSpecification.price") @@ -97,7 +97,6 @@ describe("BaseValidator", () => { const result = condition({}); expect(result.severity).to.equal("ERROR"); - expect(result.fieldName).to.equal("price"); expect(result.fieldNames).to.deep.equal([ "price", "priceSpecification.price", @@ -121,20 +120,19 @@ describe("BaseValidator", () => { ); const result = condition({}); - expect(result.fieldName).to.equal("availability"); expect(result.fieldNames).to.deep.equal(["availability"]); }); }); - describe("nested path fieldName", () => { - it("should include fieldName for nested attributes", () => { + describe("nested path fieldNames", () => { + it("should include fieldNames for nested attributes", () => { const condition = validator.required("offers.price"); const result = condition({ offers: {} }); expect(result).to.deep.include({ severity: "ERROR", - fieldName: "offers.price", }); + expect(result.fieldNames).to.deep.equal(["offers.price"]); }); }); }); diff --git a/src/types/base.js b/src/types/base.js index abaa6f3..847d413 100644 --- a/src/types/base.js +++ b/src/types/base.js @@ -58,7 +58,7 @@ export default class BaseValidator { issueMessage: `Required attribute "${name}" is missing`, severity: 'ERROR', path: this.path, - fieldName: name, + fieldNames: [name], }; } if (type && !this.checkType(value, type, ...opts)) { @@ -66,7 +66,7 @@ export default class BaseValidator { issueMessage: `Invalid type for attribute "${name}"`, severity: 'ERROR', path: this.path, - fieldName: name, + fieldNames: [name], }; } return null; @@ -94,8 +94,8 @@ export default class BaseValidator { // Collect all field names from the conditions const fieldNames = issues .flat() - .filter((i) => i && i.fieldName) - .map((i) => i.fieldName); + .filter((i) => i && i.fieldNames) + .flatMap((i) => i.fieldNames); return { issueMessage: `One of the following conditions needs to be met: ${issues @@ -104,8 +104,7 @@ export default class BaseValidator { .join(' or ')}`, severity, path: this.path, - fieldName: fieldNames[0] || null, - fieldNames: fieldNames.length > 0 ? fieldNames : undefined, + fieldNames: fieldNames.length > 0 ? fieldNames : [], }; }; } @@ -118,7 +117,7 @@ export default class BaseValidator { issueMessage: `Missing field "${name}" (optional)`, severity: 'WARNING', path: this.path, - fieldName: name, + fieldNames: [name], }; } if (type && !this.checkType(value, type, ...opts)) { @@ -126,7 +125,7 @@ export default class BaseValidator { issueMessage: `Invalid type for attribute "${name}"`, severity: 'WARNING', path: this.path, - fieldName: name, + fieldNames: [name], }; } return null; diff --git a/src/types/schemaOrg.js b/src/types/schemaOrg.js index 280b64b..e9c160b 100644 --- a/src/types/schemaOrg.js +++ b/src/types/schemaOrg.js @@ -215,7 +215,7 @@ export default class SchemaOrgValidator { severity: 'WARNING', path: this.path, errorType: 'schemaOrg', - fieldName: propertyId, + fieldNames: [propertyId], }); } }), From 765e36f5ee82ccc8baaedb04a99e1957b07f1e37 Mon Sep 17 00:00:00 2001 From: Sriram Date: Mon, 2 Feb 2026 08:56:49 +0530 Subject: [PATCH 3/3] test: add fieldNames assertions to type-specific test files Add fieldNames coverage to unit tests for: - BreadcrumbList.test.js - DefinedRegion.test.js - MerchantReturnPolicy.test.js - Product.test.js - ProductMerchant.test.js - Rating.test.js - schemaOrg.test.js This ensures the fieldNames property is verified in each validator's test file, not just in base.test.js. Co-authored-by: Cursor --- src/types/__tests__/BreadcrumbList.test.js | 3 +++ src/types/__tests__/DefinedRegion.test.js | 1 + .../__tests__/MerchantReturnPolicy.test.js | 5 ++++ src/types/__tests__/Product.test.js | 2 ++ src/types/__tests__/ProductMerchant.test.js | 1 + src/types/__tests__/Rating.test.js | 23 +++++++++++++++++++ src/types/__tests__/schemaOrg.test.js | 2 ++ 7 files changed, 37 insertions(+) diff --git a/src/types/__tests__/BreadcrumbList.test.js b/src/types/__tests__/BreadcrumbList.test.js index 71282bc..aac1dbd 100644 --- a/src/types/__tests__/BreadcrumbList.test.js +++ b/src/types/__tests__/BreadcrumbList.test.js @@ -51,6 +51,7 @@ describe('BreadcrumbListValidator', () => { issueMessage: 'At least two ListItems are required', severity: 'WARNING', path: [{ type: 'BreadcrumbList', index: 0 }], + fieldNames: ['itemListElement'], }); }); @@ -77,6 +78,7 @@ describe('BreadcrumbListValidator', () => { type: 'ListItem', }, ], + fieldNames: ['item'], }); }); @@ -215,6 +217,7 @@ describe('BreadcrumbListValidator', () => { type: 'ListItem', }, ], + fieldNames: ['item'], }); }); }); diff --git a/src/types/__tests__/DefinedRegion.test.js b/src/types/__tests__/DefinedRegion.test.js index e19e8ad..7d002de 100644 --- a/src/types/__tests__/DefinedRegion.test.js +++ b/src/types/__tests__/DefinedRegion.test.js @@ -51,6 +51,7 @@ describe('DefinedRegionValidator', () => { index: 0, }, ], + fieldNames: ['addressRegion', 'postalCode'], }); }); }); diff --git a/src/types/__tests__/MerchantReturnPolicy.test.js b/src/types/__tests__/MerchantReturnPolicy.test.js index a44a9c8..2ce8a31 100644 --- a/src/types/__tests__/MerchantReturnPolicy.test.js +++ b/src/types/__tests__/MerchantReturnPolicy.test.js @@ -74,6 +74,11 @@ describe('MerchantReturnPolicyValidator', () => { index: 0, }, ], + fieldNames: [ + 'applicableCountry', + 'returnPolicyCategory', + 'merchantReturnLink', + ], }); }); }); diff --git a/src/types/__tests__/Product.test.js b/src/types/__tests__/Product.test.js index 62b86e7..4abeaf0 100644 --- a/src/types/__tests__/Product.test.js +++ b/src/types/__tests__/Product.test.js @@ -92,6 +92,7 @@ describe('ProductValidator', () => { 'One of the following attributes is required: "aggregateRating", "offers" or "review"', location: '35,350', severity: 'ERROR', + fieldNames: ['aggregateRating', 'offers', 'review'], }); }); @@ -107,6 +108,7 @@ describe('ProductValidator', () => { 'At least 2 notes, either positive or negative, are required', location: '35,1353', severity: 'WARNING', + fieldNames: ['review.positiveNotes', 'review.negativeNotes'], }); }); diff --git a/src/types/__tests__/ProductMerchant.test.js b/src/types/__tests__/ProductMerchant.test.js index 74e29ba..045121d 100644 --- a/src/types/__tests__/ProductMerchant.test.js +++ b/src/types/__tests__/ProductMerchant.test.js @@ -84,6 +84,7 @@ describe('ProductMerchantListValidator', () => { location: '35,1236', severity: 'WARNING', path: [{ type: 'Product', index: 0 }], + fieldNames: ['gtin', 'gtin8', 'gtin12', 'gtin13', 'gtin14', 'isbn'], }); }); }); diff --git a/src/types/__tests__/Rating.test.js b/src/types/__tests__/Rating.test.js index 4089a1f..b8ce05d 100644 --- a/src/types/__tests__/Rating.test.js +++ b/src/types/__tests__/Rating.test.js @@ -28,5 +28,28 @@ describe('RatingValidator', () => { const issues = await validator.validate(data); expect(issues).to.deep.equal([]); }); + + it('should return error with fieldNames when rating is outside range', async () => { + const data = { + jsonld: { + Rating: [ + { + '@type': 'Rating', + '@location': '1,100', + ratingValue: 10, + bestRating: 5, + worstRating: 0, + }, + ], + }, + }; + const issues = await validator.validate(data); + expect(issues).to.have.lengthOf(1); + expect(issues[0]).to.deep.include({ + issueMessage: 'Rating is outside the specified or default range', + severity: 'ERROR', + fieldNames: ['ratingValue'], + }); + }); }); }); diff --git a/src/types/__tests__/schemaOrg.test.js b/src/types/__tests__/schemaOrg.test.js index 211a9be..2f52f71 100644 --- a/src/types/__tests__/schemaOrg.test.js +++ b/src/types/__tests__/schemaOrg.test.js @@ -126,6 +126,7 @@ describe('Schema.org Validator', () => { severity: 'WARNING', path: [{ type: 'Product', index: 0 }], errorType: 'schemaOrg', + fieldNames: ['my-custom-attribute'], }); }); @@ -154,6 +155,7 @@ describe('Schema.org Validator', () => { }, ], errorType: 'schemaOrg', + fieldNames: ['my-custom-attribute'], }); }); });