Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .vale.ini
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@ Vocab = Base
# Enable Markdown-specific styles.
BasedOnStyles = Vale, Google

# Skip MDX component blocks. The <HowTo> wrapper carries JSX prop values
# (step text, tool/supply names, em-dash separators in display names) that
# Vale otherwise lints as prose — flagging quote/punctuation rules and
# em-dash spacing on what is structured component data, not narrative
# copy. The two patterns cover both self-closing (<HowTo ... />) and
# paired (<HowTo>...</HowTo>) usages so future authors can use either.
# Use [\s\S] instead of `.` (Vale's regex doesn't honour `(?s)` reliably
# in BlockIgnores) and intentionally allow `>` inside the prop body —
# `[^>]` would break on JSX values like `Linux kernel >= 5.10`.
BlockIgnores = (?s)<HowTo\b[\s\S]*?/>, \
(?s)<HowTo\b[\s\S]*?</HowTo>

# Customize specific rules based on your needs.
List.Capitalization = YES

Expand Down
23 changes: 23 additions & 0 deletions docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -365,11 +365,13 @@ module.exports = {
label: "1.0.0",
path: "1.0.0",
banner: "unmaintained",
noIndex: true,
},
"2.0.0": {
label: "2.0.0",
path: "2.0.0",
banner: "unmaintained",
noIndex: true,
},
},
onlyIncludeVersions: ["1.0.0", "2.0.0", "4.0.0"],
Expand Down Expand Up @@ -464,6 +466,27 @@ module.exports = {
// 0.5 → /docs/concepts/reference/glossary/* (long-tail
// glossary; noindexed legacy versions excluded via
// netlify headers + robots.txt)
//
// Also exclude auto-generated tag indexes and the unmaintained
// 1.0.0 / 2.0.0 doc versions from the sitemap. Those versions
// additionally carry `noIndex: true` via their `versions` config
// above; excluding from the sitemap signals that they should not
// be ranked at all.
//
// Docusaurus matches `ignorePatterns` against the full route path
// including `baseUrl` (`/docs/`), so the patterns must carry that
// prefix — bare `/tags/**` and `/1.0.0/**` would never match the
// emitted `/docs/tags/...` and `/docs/1.0.0/...` routes. Bare
// patterns are kept as defence-in-depth in case `baseUrl` is ever
// flattened to `/`.
ignorePatterns: [
"/docs/tags/**",
"/docs/1.0.0/**",
"/docs/2.0.0/**",
"/tags/**",
"/1.0.0/**",
"/2.0.0/**",
Comment thread
slayerjain marked this conversation as resolved.
],
createSitemapItems: async (params) => {
const {defaultCreateSitemapItems, ...rest} = params;
const items = await defaultCreateSitemapItems(rest);
Expand Down
135 changes: 135 additions & 0 deletions src/components/HowTo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import React from "react";
import Head from "@docusaurus/Head";

/**
* HowTo schema.org wrapper for Docusaurus MDX pages.
*
* Emits valid schema.org/HowTo JSON-LD into <head> and (optionally) renders a
* matching numbered <ol> of visible steps. Authors can pass `visible={false}`
* when the prose below already renders the steps so the JSON-LD is the only
* change to the page.
*
* Required HowTo fields per Google: name, step (array of HowToStep with name + text).
* Optional: totalTime (ISO 8601 duration), estimatedCost (MonetaryAmount), tool, supply.
*
* Example:
* <HowTo
* name="Install Keploy on Linux"
* totalTime="PT5M"
* estimatedCost={{currency: "USD", value: "0"}}
* tools={["bash", "curl"]}
* supplies={["Linux machine with kernel >= 5.10"]}
* steps={[
* {name: "Download", text: "Run: curl ...", url: "#download"},
* {name: "Install", text: "Run: sudo install ...", url: "#install"},
* ]}
* visible={false}
* />
*/
export default function HowTo({
name,
description,
totalTime,
estimatedCost,
tools,
supplies,
image,
steps,
visible = true,
}) {
if (!name || !Array.isArray(steps) || steps.length === 0) {
// Component is a no-op without the minimum required fields.
return null;
}

// Filter to steps that carry both `name` and `text` per Google's HowTo
// requirements. Auto-generating "Step N" placeholders or emitting empty
// `text` produces low-quality structured data that the rich-results test
// flags. If the author gave us nothing usable, drop the schema entirely
// rather than ship a hollow HowTo.
const validSteps = steps.filter(
(s) =>
typeof s.name === "string" &&
s.name.trim() &&
typeof s.text === "string" &&
s.text.trim()
);
if (validSteps.length === 0) {
return null;
}

const schema = {
"@context": "https://schema.org",
"@type": "HowTo",
name,
step: validSteps.map((s, i) => {
const step = {
"@type": "HowToStep",
position: i + 1,
name: s.name,
text: s.text,
};
if (s.url) step.url = s.url;
if (s.image) step.image = s.image;
return step;
}),
};
Comment thread
slayerjain marked this conversation as resolved.

if (description) schema.description = description;
if (totalTime) schema.totalTime = totalTime;
if (image) schema.image = image;
if (estimatedCost && estimatedCost.value !== undefined) {
schema.estimatedCost = {
"@type": "MonetaryAmount",
currency: estimatedCost.currency || "USD",
value: String(estimatedCost.value),
};
}
if (Array.isArray(tools) && tools.length > 0) {
schema.tool = tools.map((t) =>
typeof t === "string" ? {"@type": "HowToTool", name: t} : t
);
}
if (Array.isArray(supplies) && supplies.length > 0) {
schema.supply = supplies.map((s) =>
typeof s === "string" ? {"@type": "HowToSupply", name: s} : s
);
}

return (
<>
<Head>
<script type="application/ld+json">{JSON.stringify(schema)}</script>
</Head>
{visible && (
<section
aria-label={name}
style={{
border: "1px solid var(--ifm-color-emphasis-200)",
borderRadius: "12px",
padding: "1rem 1.25rem",
margin: "1rem 0 1.5rem",
background: "var(--ifm-background-surface-color)",
}}
>
<h3 style={{marginTop: 0}}>{name}</h3>
{description && <p>{description}</p>}
<ol>
{/* Don't derive an `id` from `s.url`. In docs usage `step.url`
often points at an existing heading anchor on the page (e.g.
`#capturing-testcases`), so reusing that as a list-item id
would produce duplicate ids in the DOM whenever `visible`
is enabled. The list is the readable view; `step.url` in
the JSON-LD already covers the schema linkage. */}
{validSteps.map((s, i) => (
<li key={i}>
<strong>{s.name}</strong>
<div>{s.text}</div>
</li>
))}
</ol>
</section>
)}
</>
);
}
80 changes: 76 additions & 4 deletions src/pages/about.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,91 @@
import React from "react";
import Layout from "@theme/Layout";
import Head from "@docusaurus/Head";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import useBaseUrl from "@docusaurus/useBaseUrl";

// Custom React pages under src/pages/ are not covered by the docs schema
// plugin — add Article + BreadcrumbList JSON-LD inline so the page is
// machine-readable for search engines and AI crawlers.
//
// Site config sets `trailingSlash: true`, so canonical URLs in the JSON-LD
// must carry the trailing slash to match the actual emitted href and avoid
// duplicate URL variants in structured data.
//
// Single source of truth for the page's title and description: the Layout
// `title`/`description` props, the visible H1, and the Article JSON-LD
// `headline`/`description` all read from these constants. Previously the
// page shipped Layout title "About the docs" / description "User General
// Information about..." while the JSON-LD claimed headline "About the
// Keploy Documentation" / a different description, which confuses snippet
// generators and leaves rich-result text out of sync with the meta tags.
const ABOUT_TITLE = "About the Keploy Documentation";
const ABOUT_DESCRIPTION =
"Information about Keploy's documentation, contribution guidelines, and licensing.";

const aboutStructuredData = [
{
"@context": "https://schema.org",
"@type": "Article",
headline: ABOUT_TITLE,
description: ABOUT_DESCRIPTION,
url: "https://keploy.io/docs/about/",
publisher: {
"@type": "Organization",
name: "Keploy",
logo: {
"@type": "ImageObject",
url: "https://keploy.io/docs/img/favicon.png",
},
},
mainEntityOfPage: {
"@type": "WebPage",
"@id": "https://keploy.io/docs/about/",
},
},
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement: [
{
"@type": "ListItem",
position: 1,
name: "Home",
item: "https://keploy.io/",
},
{
"@type": "ListItem",
position: 2,
name: "Docs",
item: "https://keploy.io/docs/",
},
{
"@type": "ListItem",
position: 3,
name: "About",
item: "https://keploy.io/docs/about/",
},
],
},
];

function About() {
const context = useDocusaurusContext();
const {siteConfig = {}} = context;
return (
Comment on lines 71 to 74
<Layout
title="About the docs"
title={ABOUT_TITLE}
permalink="/about"
description="User General Information about Keploy's Documentation"
description={ABOUT_DESCRIPTION}
>
<Head>
{aboutStructuredData.map((schema, i) => (
<script key={i} type="application/ld+json">
{JSON.stringify(schema)}
</script>
))}
</Head>
<main className="margin-vert--lg container">
<h1>About the docs</h1>
<h1>{ABOUT_TITLE}</h1>
<div className="margin-bottom--lg">
<h2 id="latest">Documentation SLA</h2>
<p>
Expand Down
73 changes: 71 additions & 2 deletions src/pages/concepts/reference/glossary.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,72 @@
import React, {useState, useMemo} from "react";
import Layout from "@theme/Layout";
import Head from "@docusaurus/Head";
import BackToTopButton from "@theme/BackToTopButton";

import {glossaryEntries} from "../../../../static/data/glossaryEntries";
import GlossaryCard from "../../../components/GlossaryCard";

// SEO/GEO: turn each glossary entry into a DefinedTerm inside a single
// DefinedTermSet so AI engines can cite individual definitions and engines
// can surface them as featured-snippet definitions. Mirrors the pattern in
// landing/app/(default)/what-is-api-testing/layout.tsx.
//
// Site config sets `trailingSlash: true`, so every emitted URL must carry a
// trailing slash to match the canonical href. Otherwise Google treats the
// no-slash variant as a duplicate URL of the canonical one.
const allGlossaryItems = Object.values(glossaryEntries).flat();
const SITE = "https://keploy.io";
const GLOSSARY_PATH = "/docs/concepts/reference/glossary/";
const GLOSSARY_URL = `${SITE}${GLOSSARY_PATH}`;
const TERMSET_ID = `${GLOSSARY_URL}#termset`;

function withTrailingSlash(path) {
if (!path) return path;
return path.endsWith("/") ? path : `${path}/`;
}

const glossaryStructuredData = [
{
"@context": "https://schema.org",
"@type": "DefinedTermSet",
"@id": TERMSET_ID,
name: "Keploy Software Testing Glossary",
description:
"Definitions for software testing, test automation, and quality engineering terminology, maintained by the Keploy documentation team.",
url: GLOSSARY_URL,
// Defensive: an entry without a valid `link` (e.g. a typoed key like
// `ink:`) would emit `https://keploy.ioundefined` into the JSON-LD.
// Drop those entries here so structured data never carries a malformed
// URL even if `glossaryEntries` has gaps.
hasDefinedTerm: allGlossaryItems
.filter(
(entry) => typeof entry.link === "string" && entry.link.length > 0
)
.map((entry) => ({
"@type": "DefinedTerm",
name: entry.name,
description: entry.description,
url: `${SITE}${withTrailingSlash(entry.link)}`,
inDefinedTermSet: TERMSET_ID,
})),
},
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement: [
{"@type": "ListItem", position: 1, name: "Home", item: `${SITE}/`},
{"@type": "ListItem", position: 2, name: "Docs", item: `${SITE}/docs/`},
{
"@type": "ListItem",
position: 3,
name: "Concepts",
item: `${SITE}/docs/concepts/`,
},
{"@type": "ListItem", position: 4, name: "Glossary", item: GLOSSARY_URL},
],
},
];

function Glossary() {
const [selectedletter, setselectedletter] = useState([]);

Expand Down Expand Up @@ -37,10 +99,17 @@ function Glossary() {

return (
<Layout
title="Glossary"
title="Software Testing Glossary — Keploy Documentation"
permalink="/reference/glossary"
description="A glossary of terms related to software testing and development."
description="Definitions for software testing, test automation, and QA terminology. Acceptance, agile unit, BDD, beta, black-box testing and more."
>
<Head>
{glossaryStructuredData.map((schema, i) => (
<script key={i} type="application/ld+json">
{JSON.stringify(schema)}
</script>
))}
</Head>
<main className="container mx-auto my-12 px-4 sm:px-6 lg:px-8">
<div className="mb-12 text-center">
<h1 className="text-4xl font-extrabold tracking-tight sm:text-5xl">
Expand Down
Loading
Loading