diff --git a/.htmlvalidate.mjs b/.htmlvalidate.mjs index 735199a..350fd96 100644 --- a/.htmlvalidate.mjs +++ b/.htmlvalidate.mjs @@ -30,6 +30,7 @@ export default defineConfig({ "pacific-medical-training/latest-packages": "error", "pacific-medical-training/https-links": "error", "pacific-medical-training/internal-links": "error", + "pacific-medical-training/structured-data": "error", "wcag/h37": [ "error", { diff --git a/README.md b/README.md index e03ed2b..2821ff4 100644 --- a/README.md +++ b/README.md @@ -70,15 +70,11 @@ Perform website testing (you must have already [built the site](#build-the-site) yarn test ``` -#### Structured Data Testing - -Test structured data (JSON-LD) validation specifically: - -```sh -yarn test-structured-data -``` - -This validates that any `application/ld+json` scripts in `build/**/*.html` files have correct schema.org formats and valid JSON syntax. +This includes validation of: +- HTML structure and accessibility +- External and internal links +- Structured data (JSON-LD) validation using schema.org standards +- Content quality checks ## Notes for VS Code diff --git a/package.json b/package.json index 1a76422..e298a1d 100644 --- a/package.json +++ b/package.json @@ -13,9 +13,8 @@ "structured-data-testing-tool": "^4.5.0" }, "scripts": { - "test": "yarn node test/build-html-validate.mjs && yarn node test/dirty-file-paths-checker.mjs && yarn node test/build-structured-data-validate.mjs", - "test-test": "yarn node test/fixtures-html-validate-should-fail.mjs && yarn node test/fixtures-structured-data-should-fail.mjs", - "test-structured-data": "yarn node test/build-structured-data-validate.mjs", + "test": "yarn node test/build-html-validate.mjs && yarn node test/dirty-file-paths-checker.mjs", + "test-test": "yarn node test/fixtures-html-validate-should-fail.mjs", "lint": "yarn prettier-check && yarn markdownlint-check", "lint-fix": "yarn prettier-fix && yarn markdownlint-fix", "generate-sitemap": "node scripts/generate-sitemap.mjs", diff --git a/test/build-structured-data-validate.mjs b/test/build-structured-data-validate.mjs deleted file mode 100644 index 87b0b17..0000000 --- a/test/build-structured-data-validate.mjs +++ /dev/null @@ -1,65 +0,0 @@ -import { glob } from "glob"; -import { execSync } from "child_process"; -import fs from "fs"; -import path from "path"; - -// Find and sort all HTML files in the 'build' directory -const targets = glob.sync("build/**/*.html").sort(); - -if (targets.length === 0) { - console.log("โ ๏ธ No HTML files found in build directory"); - console.log(" Make sure to build the site first"); - process.exit(0); -} - -console.log(`๐งช Validating structured data in ${targets.length} files...`); - -let hasErrors = false; - -// Validate each target file -for (const target of targets) { - try { - // Use structured-data-testing-tool CLI to test the file - const result = execSync(`yarn dlx structured-data-testing-tool --file "${target}"`, { - encoding: "utf8", - stdio: "pipe", - }); - - // Check if there are JSON-LD parse errors or failed tests in the output - if (result.includes("Error in jsonld parse") || (result.includes("Failed:") && !result.includes("Failed: 0"))) { - console.log("โ " + target); - console.log(result); - hasErrors = true; - } else { - console.log("โ " + target); - // Show a summary of structured data found - const lines = result.split("\n"); - const schemaLine = lines.find((line) => line.includes("Schema.org schemas:")); - if (schemaLine && !schemaLine.includes("Schema.org schemas: 0")) { - console.log(` ๐ ${schemaLine.trim()}`); - } - // Only show detailed output if there are warnings - if (result.includes("Warnings:") && !result.includes("Warnings: 0")) { - console.log("โ ๏ธ Warnings found:"); - console.log(result); - } - } - } catch (error) { - console.log("โ " + target); - console.log("โ ๏ธ Error testing file:", error.message); - if (error.stdout) { - console.log(error.stdout); - } - if (error.stderr) { - console.error(error.stderr); - } - hasErrors = true; - } -} - -if (hasErrors) { - console.log("โ Some tests failed."); - process.exit(1); -} else { - console.log("โจ All tests passed!\n"); -} diff --git a/test/fixtures-structured-data-should-fail.mjs b/test/fixtures-structured-data-should-fail.mjs deleted file mode 100644 index 88dd473..0000000 --- a/test/fixtures-structured-data-should-fail.mjs +++ /dev/null @@ -1,58 +0,0 @@ -import { execSync } from "child_process"; -import path from "path"; - -console.log("๐งช Testing structured data fixtures"); - -const fixtures = [ - { - file: "test/fixtures/valid-jsonld.html", - shouldPass: true, - description: "valid JSON-LD", - }, - { - file: "test/fixtures/invalid-jsonld.html", - shouldPass: false, - description: "invalid JSON-LD (syntax error)", - }, -]; - -let allTestsPassed = true; - -for (const fixture of fixtures) { - try { - const result = execSync(`yarn dlx structured-data-testing-tool --file "${fixture.file}"`, { - encoding: "utf8", - stdio: "pipe", - }); - - const hasError = - result.includes("Error in jsonld parse") || (result.includes("Failed:") && !result.includes("Failed: 0")); - - const actuallyPassed = !hasError; - - if (actuallyPassed === fixture.shouldPass) { - console.log(`โ ${fixture.file}: ${fixture.description} - ${actuallyPassed ? "passed" : "failed"} as expected`); - } else { - console.error( - `โ ${fixture.file}: ${fixture.description} - expected ${fixture.shouldPass ? "pass" : "fail"} but got ${actuallyPassed ? "pass" : "fail"}`, - ); - console.error("Output:", result); - allTestsPassed = false; - } - } catch (error) { - // execSync throws on non-zero exit codes, but structured-data-testing-tool doesn't always exit with error codes - console.error(`โ ${fixture.file}: ${fixture.description} - execution error`); - console.error("Error:", error.message); - if (error.stdout) { - console.error("Stdout:", error.stdout); - } - allTestsPassed = false; - } -} - -if (allTestsPassed) { - console.log("โจ All fixtures produced expected results!\n"); -} else { - console.error("โ Some fixture tests failed."); - process.exit(1); -} diff --git a/test/fixtures/required-results.json b/test/fixtures/required-results.json index 3944351..a166bc7 100644 --- a/test/fixtures/required-results.json +++ b/test/fixtures/required-results.json @@ -166,6 +166,29 @@ "ruleUrl": "https://html-validate.org/rules/wcag/h37.html" } ], + "test/fixtures/valid-jsonld.html": [ + { + "ruleId": "pacific-medical-training/canonical-link", + "severity": 2, + "message": "
is missing ", + "size": 0, + "selector": null, + "ruleUrl": "https://github.com/fulldecent/github-pages-template/#canonical" + } + ], + "test/fixtures/invalid-jsonld.html": [ + { + "ruleId": "pacific-medical-training/structured-data", + "severity": 2, + "message": "JSON-LD parse error: SyntaxError: Expected property name or '}' in JSON at position 11", + "offset": 279, + "line": 11, + "column": 39, + "size": 1, + "selector": "html > body > script", + "ruleUrl": "https://github.com/fulldecent/github-pages-template/#structured-data" + } + ], "test/fixtures/directory-path-links-test.html": [ { "ruleId": "pacific-medical-training/internal-links", diff --git a/test/plugin.html-validate.mjs b/test/plugin.html-validate.mjs index 57e4ed9..cdbd0ed 100644 --- a/test/plugin.html-validate.mjs +++ b/test/plugin.html-validate.mjs @@ -7,6 +7,7 @@ import CanonicalLinkRule from "./plugin.html-validate.canonical-link.mjs"; import LatestPackagesRules from "./plugin.html-validate.latest-packages.mjs"; import EnsureHttpsRules from "./plugin.html-validate.https-links.mjs"; import CheckInternalLinks from "./plugin.html-validate.internal-links.mjs"; +import StructuredDataRule from "./plugin.html-validate.structured-data.mjs"; export default definePlugin({ name: "pacific-medical-training", @@ -18,6 +19,7 @@ export default definePlugin({ "pacific-medical-training/latest-packages": LatestPackagesRules, "pacific-medical-training/https-links": EnsureHttpsRules, "pacific-medical-training/internal-links": CheckInternalLinks, + "pacific-medical-training/structured-data": StructuredDataRule, }, configs: { recommended: { @@ -29,6 +31,7 @@ export default definePlugin({ "pacific-medical-training/latest-packages": "error", "pacific-medical-training/https-links": "error", "pacific-medical-training/internal-links": "error", + "pacific-medical-training/structured-data": "error", }, }, }, diff --git a/test/plugin.html-validate.structured-data.mjs b/test/plugin.html-validate.structured-data.mjs new file mode 100644 index 0000000..46307c5 --- /dev/null +++ b/test/plugin.html-validate.structured-data.mjs @@ -0,0 +1,176 @@ +import { Rule } from "html-validate"; +import { execSync } from "child_process"; +import fs from "fs"; +import path from "path"; + +export default class StructuredDataRule extends Rule { + documentation() { + return { + description: "Validate JSON-LD structured data using structured-data-testing-tool", + url: "https://github.com/fulldecent/github-pages-template/#structured-data", + }; + } + + setup() { + this.on("tag:ready", this.tagReady.bind(this)); + } + + tagReady({ target }) { + if (target.tagName === "script") { + const type = target.getAttribute("type")?.value; + + // Only process script tags with type="application/ld+json" + if (type === "application/ld+json") { + this.validateJsonLd(target); + } + } + } + + validateJsonLd(scriptElement) { + // Try to read the file content directly and extract the script + if (scriptElement.location && scriptElement.location.filename) { + try { + const fileContent = fs.readFileSync(scriptElement.location.filename, "utf8"); + const lines = fileContent.split("\n"); + + // Find script tag boundaries + let startLine = -1; + let endLine = -1; + let inScript = false; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + if (line.includes('")) { + endLine = i; + break; + } + } + + if (startLine >= 0 && endLine >= 0) { + const scriptContent = lines.slice(startLine, endLine).join("\n").trim(); + + if (scriptContent) { + this.testStructuredData(scriptContent, scriptElement); + return; + } + } + } catch (error) { + this.report({ + node: scriptElement, + message: `Error reading file for structured data validation: ${error.message}`, + }); + return; + } + } + + this.report({ + node: scriptElement, + message: "JSON-LD script tag is empty or cannot be read", + }); + } + + testStructuredData(content, scriptElement) { + // Create a temporary HTML file with just this JSON-LD script + const tempDir = "/tmp"; + const tempFileName = `structured-data-${Date.now()}-${Math.random().toString(36).substr(2, 9)}.html`; + const tempFilePath = path.join(tempDir, tempFileName); + + try { + // Create minimal HTML with just the JSON-LD script + const tempHtml = ` + + +