From b79b0a7e162edd18b0fec1a0929061b68c851fa3 Mon Sep 17 00:00:00 2001 From: Dylan Depass Date: Mon, 28 Oct 2024 16:14:34 -0400 Subject: [PATCH] fix: add some test coverage of rendered html --- package-lock.json | 336 +++++++++++++++++++++++++++++++++++++ package.json | 1 + test/fixtures/product.js | 103 ++++++++++++ test/fixtures/variant.js | 74 ++++++++ test/template/html.test.js | 254 ++++++++++++++++++++++++++++ 5 files changed, 768 insertions(+) create mode 100644 test/fixtures/product.js create mode 100644 test/fixtures/variant.js create mode 100644 test/template/html.test.js diff --git a/package-lock.json b/package-lock.json index 36891eb..4ac1ac1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "eslint-plugin-import": "2.29.1", "esmock": "^2.6.9", "husky": "9.1.4", + "jsdom": "^25.0.1", "lint-staged": "15.2.9", "mocha": "10.7.3", "mocha-multi-reporters": "1.5.1", @@ -1626,6 +1627,12 @@ "printable-characters": "^1.0.42" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -2122,6 +2129,18 @@ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "12.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", @@ -2339,12 +2358,37 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cssstyle": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.1.0.tgz", + "integrity": "sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA==", + "dev": true, + "dependencies": { + "rrweb-cssom": "^0.7.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/data-uri-to-buffer": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz", "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==", "dev": true }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/data-view-buffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", @@ -2435,6 +2479,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "dev": true + }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -2490,6 +2540,15 @@ "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", "dev": true }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/diff": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", @@ -2574,6 +2633,18 @@ "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", "dev": true }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/env-ci": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-11.1.0.tgz", @@ -3497,6 +3568,20 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/from2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", @@ -3978,6 +4063,18 @@ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -4040,6 +4137,18 @@ "url": "https://github.com/sponsors/typicode" } }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -4386,6 +4495,12 @@ "node": ">=8" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -4604,6 +4719,58 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "25.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz", + "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==", + "dev": true, + "dependencies": { + "cssstyle": "^4.1.0", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.12", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.7.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^2.11.2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "dev": true, + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -5263,6 +5430,27 @@ "node": ">=16" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -8238,6 +8426,12 @@ "inBundle": true, "license": "ISC" }, + "node_modules/nwsapi": { + "version": "2.2.13", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.13.tgz", + "integrity": "sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ==", + "dev": true + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -9173,6 +9367,12 @@ "estree-walker": "^0.6.1" } }, + "node_modules/rrweb-cssom": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", + "dev": true + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -9237,6 +9437,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/selfsigned": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", @@ -10183,6 +10401,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, "node_modules/temp-dir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", @@ -10344,6 +10568,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/tldts": { + "version": "6.1.56", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.56.tgz", + "integrity": "sha512-2PT1oRZCxtsbLi5R2SQjE/v4vvgRggAtVcYj+3Rrcnu2nPZvu7m64+gDa/EsVSWd3QzEc0U0xN+rbEKsJC47kA==", + "dev": true, + "dependencies": { + "tldts-core": "^6.1.56" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.56", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.56.tgz", + "integrity": "sha512-Ihxv/Bwiyj73icTYVgBUkQ3wstlCglLoegSgl64oSrGUBX1hc7Qmf/CnrnJLaQdZrCnTaLqMYOwKMKlkfkFrxQ==", + "dev": true + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -10356,6 +10598,30 @@ "node": ">=8.0" } }, + "node_modules/tough-cookie": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz", + "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==", + "dev": true, + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "dev": true, + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/traverse": { "version": "0.6.8", "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.8.tgz", @@ -10670,6 +10936,61 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz", + "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==", + "dev": true, + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -10988,6 +11309,21 @@ } } }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index 2d32da9..0b20614 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "eslint-plugin-import": "2.29.1", "esmock": "^2.6.9", "husky": "9.1.4", + "jsdom": "25.0.1", "lint-staged": "15.2.9", "mocha": "10.7.3", "mocha-multi-reporters": "1.5.1", diff --git a/test/fixtures/product.js b/test/fixtures/product.js new file mode 100644 index 0000000..c494ab2 --- /dev/null +++ b/test/fixtures/product.js @@ -0,0 +1,103 @@ +/* + * Copyright 2024 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. + */ + +/** + * Generates a configurable product fixture. + * @param {Object} overrides - An object containing properties to override. + * @returns {Object} - The product fixture. + */ +export function createProductFixture(overrides = {}) { + const product = { + sku: 'test-sku', + name: 'Test Product Name', + metaTitle: 'Test Product - test-sku | Test Brand', + metaDescription: 'Experience the excellence of our Test Product, engineered to meet all your everyday needs with unmatched reliability and performance. Discover quality and innovation that sets you apart, available now at unbeatable prices.', + metaKeyword: 'Keyword 1, Keyword 2, Keyword 3', + description: 'Introducing our Test Product, designed to deliver exceptional performance and reliability for all your daily tasks. Crafted with premium materials, it ensures durability and a sleek, modern aesthetic that complements any environment. Whether you’re using it at home or in the office, its user-friendly features make it effortless to operate and maintain. Experience unparalleled functionality combined with innovative technology that sets our Test Product apart from the competition. Upgrade your lifestyle today with a product that promises quality, efficiency, and outstanding value.', + url: 'https://www.example.com/products/test-product-url-key', + urlKey: 'test-product-url-key', + shortDescription: '', + addToCartAllowed: true, + inStock: true, + externalId: '123456', + images: [ + { + url: 'https://www.example.com/media/catalog/product/t/s/test-sku.png', + label: '', + }, + ], + attributes: [ + { + name: 'color_options', + label: 'Color Options', + value: 'Matte Black', + }, + { + name: 'material_type', + label: 'Material Type', + value: 'Brushed Aluminum', + }, + { + name: 'warranty_period', + label: 'Warranty Period', + value: '2 Years', + }, + { + name: 'country_of_origin', + label: 'Country of Origin', + value: 'USA', + }, + { + name: 'weight', + label: 'Weight', + value: '1.5 lbs', + }, + ], + options: [ + { + id: 'finish_filter', + label: 'Finish', + typename: 'ProductViewOptionValueConfiguration', + required: false, + multiple: null, + items: [ + { id: 'Y29uZmlndXJhYmxlLzI0NjEvMzU1MzE=', label: 'Label 1', inStock: true }, + { id: 'Y29uZmlndXJhYmxlLzI0NjEvMzYxMTE=', label: 'Label 2', inStock: true }, + { id: 'Y29uZmlndXJhYmxlLzI0NjEvMzYzNDE=', label: 'Label 3', inStock: true }, + ], + }, + { + id: 'shade_filter', + label: 'Shade', + typename: 'ProductViewOptionValueConfiguration', + required: false, + multiple: null, + items: [ + { id: 'Y29uZmlndXJhYmxlLzE2NTEvODI3MQ==', label: 'Label 4', inStock: true }, + ], + }, + ], + prices: { + regular: { + amount: 799, currency: 'USD', maximumAmount: 799, minimumAmount: 799, + }, + final: { + amount: 799, currency: 'USD', maximumAmount: 799, minimumAmount: 799, + }, + visible: true, + }, + ...overrides, + }; + + // Deep merge defaults with overrides + return product; +} diff --git a/test/fixtures/variant.js b/test/fixtures/variant.js new file mode 100644 index 0000000..4fcbe28 --- /dev/null +++ b/test/fixtures/variant.js @@ -0,0 +1,74 @@ +/* + * Copyright 2024 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. + */ + +/** + * Generates a product variation fixture. + * @param {Object} overrides - An object containing properties to override. + * @returns {Object} - The product variation fixture. + */ +export function createProductVariationFixture(overrides = {}) { + const variation = { + name: 'Test Product Name', + sku: 'test-sku-1', + description: 'Test Product Description', + inStock: true, + images: [ + { + url: 'https://www.example.com/media/catalog/product/t/s/test-variation.png', + label: 'Test Variation Label', + }, + ], + attributes: [ + { name: 'criteria_1', label: 'Details', value: 'O/A Height: 50"' }, + { name: 'criteria_2', label: 'Criteria 2', value: 'Fixture Height: 14.25"' }, + { name: 'criteria_3', label: 'Criteria 3', value: 'Min. Custom Height: 20"' }, + { name: 'criteria_4', label: 'Criteria 4', value: 'Width: 4"' }, + { name: 'criteria_5', label: 'Criteria 5', value: 'Canopy: 4.5" Round' }, + { name: 'criteria_6', label: 'Criteria 6', value: 'Socket: E26 Keyless' }, + { name: 'criteria_7', label: 'Criteria 7', value: 'Wattage: 40 T10' }, + { name: 'weight', label: 'Weight', value: 219 }, + ], + prices: { + regular: { + amount: 799, + currency: 'USD', + maximumAmount: 799, + minimumAmount: 799, + }, + final: { + amount: 799, + currency: 'USD', + maximumAmount: 799, + minimumAmount: 799, + }, + }, + selections: [ + 'Y29uZmlndXJhYmxlLzE2NTEvODI3MQ==', + 'Y29uZmlndXJhYmxlLzI0NjEvMzYzNDE=', + ], + ...overrides, + }; + + return variation; +} + +/** + * Generates a default set of product variations. + * @returns {Array} - An array of product variation fixtures. + */ +export function createDefaultVariations() { + return [ + createProductVariationFixture({ sku: 'test-sku-1' }), + createProductVariationFixture({ sku: 'test-sku-2' }), + createProductVariationFixture({ sku: 'test-sku-3' }), + ]; +} diff --git a/test/template/html.test.js b/test/template/html.test.js new file mode 100644 index 0000000..c3a1dd4 --- /dev/null +++ b/test/template/html.test.js @@ -0,0 +1,254 @@ +/* + * Copyright 2024 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. + */ + +/* eslint-disable import/no-extraneous-dependencies, max-len */ + +import assert from 'node:assert'; +import { JSDOM } from 'jsdom'; +import { createDefaultVariations } from '../fixtures/variant.js'; +import { createProductFixture } from '../fixtures/product.js'; +import htmlTemplate from '../../src/templates/html.js'; + +// Helper function to format price range +function priceRange(min, max) { + if (min === undefined || max === undefined || min === max) return ''; + return ` - ${min} to ${max}`; +} + +describe('Render Product HTML', () => { + let dom; + let document; + let product; + let variations; + + before(() => { + product = createProductFixture(); + variations = createDefaultVariations(); + const html = htmlTemplate(product, variations); + dom = new JSDOM(html); + document = dom.window.document; + }); + + it('should have the correct ', () => { + const title = document.querySelector('title'); + const expectedTitle = product.metaTitle || product.name; + assert.strictEqual(title.textContent, expectedTitle, 'Title tag does not match expected value'); + }); + + it('should have the correct meta description', () => { + const metaDescription = document.querySelector('meta[property="description"]'); + const expectedDescription = product.metaDescription || product.description; + assert.strictEqual(metaDescription.getAttribute('content'), expectedDescription, 'Meta description does not match expected value'); + }); + + it('should have the correct Open Graph title', () => { + const ogTitle = document.querySelector('meta[property="og:title"]'); + const expectedOGTitle = product.metaTitle || product.name; + assert.strictEqual(ogTitle.getAttribute('content'), expectedOGTitle, 'Open Graph title does not match expected value'); + }); + + it('should have the correct Open Graph image', () => { + const ogImage = document.querySelector('meta[property="og:image"]'); + const expectedImage = product.images[0]?.url || ''; + assert.strictEqual(ogImage.getAttribute('content'), expectedImage, 'Open Graph image does not match expected value'); + }); + + it('should have the correct Twitter description', () => { + const twitterDescription = document.querySelector('meta[name="twitter:description"]'); + const expectedDescription = product.metaDescription || product.description; + assert.strictEqual(twitterDescription.getAttribute('content'), expectedDescription, 'Twitter description does not match expected value'); + }); + + it('should have the correct JSON-LD schema', () => { + const jsonLdScript = document.querySelector('script[type="application/ld+json"]'); + assert.ok(jsonLdScript, 'JSON-LD script tag should exist'); + + const jsonLd = JSON.parse(jsonLdScript.textContent); + assert.strictEqual(jsonLd['@type'], 'Product', 'JSON-LD @type should be Product'); + assert.strictEqual(jsonLd.name, product.name, 'JSON-LD name does not match product name'); + assert.strictEqual(jsonLd.sku, product.sku, 'JSON-LD SKU does not match product SKU'); + assert.strictEqual(jsonLd.description, product.description, 'JSON-LD description does not match product description'); + assert.strictEqual(jsonLd.image, product.images[0]?.url || '', 'JSON-LD image does not match product image'); + assert.strictEqual(jsonLd.productID, product.sku, 'JSON-LD productID does not match product SKU'); + assert.ok(Array.isArray(jsonLd.offers), 'JSON-LD offers should be an array'); + assert.strictEqual(jsonLd.offers.length, variations.length + 1, 'JSON-LD offers length does not match number of variants'); + + jsonLd.offers.forEach((offer, index) => { + const variant = index === 0 ? product : variations[index - 1]; + assert.strictEqual(offer['@type'], 'Offer', `Offer type for variant ${variant.sku} should be Offer`); + assert.strictEqual(offer.sku, variant.sku, `Offer SKU for variant ${variant.sku} does not match`); + assert.strictEqual(offer.price, variant.prices.final.amount, `Offer price for variant ${variant.sku} does not match`); + assert.strictEqual(offer.priceCurrency, variant.prices.final.currency, `Offer priceCurrency for variant ${variant.sku} does not match`); + assert.strictEqual(offer.availability, variant.inStock ? 'InStock' : 'OutOfStock', `Offer availability for variant ${variant.sku} does not match`); + assert.strictEqual(offer.image, variant.images[0].url || '', `Offer image for variant ${variant.sku} does not match`); + }); + }); + + it('should display the correct product name in <h1>', () => { + const h1 = document.querySelector('h1'); + assert.strictEqual(h1.textContent, product.name, '<h1> content does not match product name'); + }); + + it('should display the correct product description in <p>', () => { + const p = document.querySelector('main > div > p'); + assert.strictEqual(p.textContent, product.description, '<p> content does not match product description'); + }); + + it('should display all product images correctly', () => { + const productImages = document.querySelectorAll('.product-images img'); + assert.strictEqual(productImages.length, product.images.length, `Number of product images (${productImages.length}) does not match expected (${product.images.length})`); + + product.images.forEach((img, index) => { + const renderedImg = productImages[index]; + assert.strictEqual(renderedImg.getAttribute('src'), img.url, `Image ${index + 1} src does not match`); + assert.strictEqual(renderedImg.getAttribute('alt'), img.label, `Image ${index + 1} alt text does not match`); + }); + }); + + it('should display all product attributes correctly', () => { + const attributeDivs = document.querySelectorAll('.product-attributes > div'); + assert.strictEqual(attributeDivs.length, product.attributes.length, `Number of product attributes (${attributeDivs.length}) does not match expected (${product.attributes.length})`); + + product.attributes.forEach((attr) => { + const matchingDiv = Array.from(attributeDivs).find( + (div) => div.children[0].textContent === attr.name, + ); + assert.ok(matchingDiv, `Attribute ${attr.name} should exist`); + assert.strictEqual(matchingDiv.children[1].textContent, attr.label, `Attribute ${attr.name} label does not match`); + assert.strictEqual(matchingDiv.children[2].textContent, String(attr.value), `Attribute ${attr.name} value does not match`); + }); + }); + + it('should display all product options correctly', () => { + const optionDivs = document.querySelectorAll('.product-options > div'); + // Calculate the expected number of option divs + const expectedOptionDivs = product.options.reduce((acc, opt) => acc + 1 + (opt.items ? opt.items.length : 0), 0); + assert.strictEqual(optionDivs.length, expectedOptionDivs, `Number of product option divs (${optionDivs.length}) does not match expected (${expectedOptionDivs})`); + + product.options.forEach((opt) => { + // Option container + const optionContainer = Array.from(optionDivs).find( + (div) => div.children[0].textContent === opt.id, + ); + assert.ok(optionContainer, `Option container for ${opt.id} should exist`); + assert.strictEqual(optionContainer.children[1].textContent, opt.label, `Option ${opt.id} label does not match`); + assert.strictEqual(optionContainer.children[2].textContent, opt.typename, `Option ${opt.id} typename does not match`); + assert.strictEqual(optionContainer.children[3].textContent, opt.type || '', `Option ${opt.id} type does not match`); + assert.strictEqual(optionContainer.children[4].textContent, opt.multiple ? 'multiple' : '', `Option ${opt.id} multiple attribute does not match`); + assert.strictEqual(optionContainer.children[5].textContent, opt.required ? 'required' : '', `Option ${opt.id} required attribute does not match`); + + // Option items + if (opt.items) { + opt.items.forEach((item) => { + const itemDiv = Array.from(optionDivs).find( + (div) => div.children[1].textContent === item.id, + ); + assert.ok(itemDiv, `Option item with ID ${item.id} should exist`); + assert.strictEqual(itemDiv.children[2].textContent, item.label, `Option item ${item.id} label does not match`); + assert.strictEqual(itemDiv.children[3].textContent, item.value || '', `Option item ${item.id} value does not match`); + assert.strictEqual(itemDiv.children[4].textContent, item.selected ? 'selected' : '', `Option item ${item.id} selected attribute does not match`); + assert.strictEqual(itemDiv.children[5].textContent, item.inStock ? 'inStock' : '', `Option item ${item.id} inStock attribute does not match`); + }); + } + }); + }); + + it('should display all product variants correctly', () => { + const variantDivs = document.querySelectorAll('.product-variants > div'); + assert.strictEqual(variantDivs.length, variations.length, `Number of product variants (${variantDivs.length}) does not match expected (${variations.length})`); + + variations.forEach((variant, index) => { + const variantDiv = variantDivs[index]; + + // SKU + const variantSKU = variantDiv.querySelector('div:nth-child(1)'); + assert.strictEqual(variantSKU.textContent, variant.sku, `Variant ${index + 1} SKU does not match`); + + // Variant Name + const variantName = variantDiv.querySelector('div:nth-child(2)'); + assert.strictEqual(variantName.textContent, variant.name, `Variant ${index + 1} name does not match`); + + // Variant Description + const variantDescription = variantDiv.querySelector('div:nth-child(3)'); + assert.strictEqual(variantDescription.textContent, variant.description, `Variant ${index + 1} description does not match`); + + // Availability + const variantAvailability = variantDiv.querySelector('div:nth-child(4)'); + assert.strictEqual( + variantAvailability.textContent, + variant.inStock ? 'inStock' : '', + `Variant ${index + 1} availability does not match`, + ); + + // Regular Price + const variantRegularPrice = variantDiv.querySelector('div:nth-child(5)'); + const expectedRegularPrice = `Regular: ${variant.prices.regular.amount} ${variant.prices.regular.currency}${priceRange(variant.prices.regular.minimumAmount, variant.prices.regular.maximumAmount)}`; + assert.strictEqual( + variantRegularPrice.textContent, + expectedRegularPrice, + `Variant ${index + 1} regular price does not match`, + ); + + // Final Price + const variantFinalPrice = variantDiv.querySelector('div:nth-child(6)'); + const expectedFinalPrice = `Final: ${variant.prices.final.amount} ${variant.prices.final.currency}${priceRange(variant.prices.final.minimumAmount, variant.prices.final.maximumAmount)}`; + assert.strictEqual( + variantFinalPrice.textContent, + expectedFinalPrice, + `Variant ${index + 1} final price does not match`, + ); + + // Variant Images + const variantImages = variantDiv.querySelectorAll('picture img'); + assert.strictEqual( + variantImages.length, + variant.images.length, + `Variant ${index + 1} should have ${variant.images.length} images`, + ); + variant.images.forEach((img, imgIndex) => { + const renderedImg = variantImages[imgIndex]; + assert.strictEqual(renderedImg.getAttribute('src'), img.url, `Variant ${index + 1} Image ${imgIndex + 1} src does not match`); + assert.strictEqual(renderedImg.getAttribute('alt'), img.label, `Variant ${index + 1} Image ${imgIndex + 1} alt text does not match`); + }); + + // Selections + const variantSelections = variantDiv.querySelector('div:nth-child(8)'); + assert.strictEqual( + variantSelections.textContent, + variant.selections.join(', '), + `Variant ${index + 1} selections do not match`, + ); + }); + }); + + it('should display all variant attributes correctly', () => { + const variantAttributeDivs = document.querySelectorAll('.variant-attributes > div'); + + variations.forEach((variant) => { + // SKU Header + const skuHeaderDiv = Array.from(variantAttributeDivs).find( + (div) => div.children[0].textContent === 'sku' && div.children[1].textContent === variant.sku, + ); + assert.ok(skuHeaderDiv, `Variant attribute header for SKU ${variant.sku} should exist`); + + // Variant Attributes + variant.attributes.forEach((attr) => { + const matchingDiv = Array.from(variantAttributeDivs).find( + (div) => div.children[1].textContent === attr.name && div.children[0].textContent === 'attribute', + ); + assert.ok(matchingDiv, `Variant attribute ${attr.name} should exist`); + assert.strictEqual(matchingDiv.children[2].textContent, attr.label, `Variant attribute ${attr.name} label does not match`); + assert.strictEqual(matchingDiv.children[3].textContent, String(attr.value), `Variant attribute ${attr.name} value does not match`); + }); + }); + }); +});