feat: harden portfolio and add SEO foundations
This commit is contained in:
1 parent
b079afdd73
commit
62bcfbd8fa
121 files changed
+7769
-10469
No files matched your search
+121
@@ -0,0 +1,121 @@
|
||||
import sitemap from "../app/sitemap";
|
||||
import {
|
||||
buildBreadcrumbList,
|
||||
buildPersonJsonLd,
|
||||
buildProfilePageJsonLd,
|
||||
buildServiceJsonLd,
|
||||
buildWebSiteJsonLd,
|
||||
SEO_IDS,
|
||||
} from "./seo";
|
||||
import { caseStudies, servicePages, siteName, siteUrl } from "./site-content";
|
||||
|
||||
const representativeService = servicePages[0];
|
||||
if (!representativeService) throw new Error("Missing representative service");
|
||||
|
||||
it("uses one canonical identity across website, profile and service", () => {
|
||||
expect(SEO_IDS).toEqual({
|
||||
website: `${siteUrl}/#website`,
|
||||
person: `${siteUrl}/#person`,
|
||||
profilePage: `${siteUrl}/curriculo#profile-page`,
|
||||
});
|
||||
expect(buildWebSiteJsonLd().publisher).toEqual({ "@id": SEO_IDS.person });
|
||||
expect(buildProfilePageJsonLd()).toMatchObject({
|
||||
"@id": SEO_IDS.profilePage,
|
||||
isPartOf: { "@id": SEO_IDS.website },
|
||||
mainEntity: { "@id": SEO_IDS.person },
|
||||
});
|
||||
expect(buildServiceJsonLd(representativeService).provider).toEqual({
|
||||
"@id": SEO_IDS.person,
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps the root graph limited to verified website and person fields", () => {
|
||||
const website = buildWebSiteJsonLd();
|
||||
const person = buildPersonJsonLd();
|
||||
|
||||
expect(Object.keys(website).sort()).toEqual(
|
||||
["@id", "@type", "inLanguage", "name", "publisher", "url"].sort(),
|
||||
);
|
||||
expect(Object.keys(person).sort()).toEqual(
|
||||
["@id", "@type", "homeLocation", "image", "name", "sameAs", "url"].sort(),
|
||||
);
|
||||
expect(website).toMatchObject({
|
||||
"@type": "WebSite",
|
||||
"@id": SEO_IDS.website,
|
||||
url: siteUrl,
|
||||
name: siteName,
|
||||
});
|
||||
expect(person).toMatchObject({
|
||||
"@type": "Person",
|
||||
"@id": SEO_IDS.person,
|
||||
image: `${siteUrl}/edev.jpg`,
|
||||
homeLocation: { "@type": "Place", name: "Garuva, Santa Catarina, Brasil" },
|
||||
sameAs: [
|
||||
"https://github.com/evandrodevbr",
|
||||
"https://www.linkedin.com/in/evandro-fonseca-junior-0969aa1b0/",
|
||||
],
|
||||
});
|
||||
|
||||
expect(JSON.stringify([website, person])).not.toMatch(
|
||||
/ProfessionalService|LocalBusiness|GeoCoordinates|PostalAddress|Instagram|telephone|address|geo|priceRange|OfferCatalog|hasOfferCatalog|offers/i,
|
||||
);
|
||||
});
|
||||
|
||||
it("builds a canonical service with only the verified service area", () => {
|
||||
const service = representativeService;
|
||||
const pageUrl = `${siteUrl}/servicos/${service.slug}`;
|
||||
|
||||
expect(buildServiceJsonLd(service)).toEqual({
|
||||
"@type": "Service",
|
||||
"@id": `${pageUrl}#service`,
|
||||
url: pageUrl,
|
||||
name: service.navLabel,
|
||||
description: service.metaDescription,
|
||||
provider: { "@id": SEO_IDS.person },
|
||||
areaServed: [
|
||||
{ "@type": "City", name: "Garuva" },
|
||||
{ "@type": "AdministrativeArea", name: "Norte de Santa Catarina" },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("builds contiguous breadcrumbs from readonly tuples", () => {
|
||||
const pageUrl = `${siteUrl}/servicos/x`;
|
||||
const items = [
|
||||
["Início", siteUrl],
|
||||
["Serviços", `${siteUrl}/servicos`],
|
||||
["X", pageUrl],
|
||||
] as const;
|
||||
const breadcrumb = buildBreadcrumbList(pageUrl, items);
|
||||
|
||||
expect(breadcrumb["@id"]).toBe(`${pageUrl}#breadcrumb`);
|
||||
expect(breadcrumb.itemListElement).toEqual([
|
||||
{ "@type": "ListItem", position: 1, name: "Início", item: siteUrl },
|
||||
{
|
||||
"@type": "ListItem",
|
||||
position: 2,
|
||||
name: "Serviços",
|
||||
item: `${siteUrl}/servicos`,
|
||||
},
|
||||
{ "@type": "ListItem", position: 3, name: "X", item: pageUrl },
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps profile data timeless and sitemap output deterministic", () => {
|
||||
expect(buildProfilePageJsonLd()).not.toHaveProperty("dateModified");
|
||||
|
||||
const expectedUrls = [
|
||||
siteUrl,
|
||||
`${siteUrl}/curriculo`,
|
||||
`${siteUrl}/servicos`,
|
||||
`${siteUrl}/cases`,
|
||||
...servicePages.map(({ slug }) => `${siteUrl}/servicos/${slug}`),
|
||||
...caseStudies.map(({ id }) => `${siteUrl}/cases/${id}`),
|
||||
];
|
||||
const first = sitemap();
|
||||
const second = sitemap();
|
||||
|
||||
expect(first).toEqual(expectedUrls.map((url) => ({ url })));
|
||||
expect(second).toEqual(first);
|
||||
expect(first).toHaveLength(16);
|
||||
});
|
||||
Reference in new issue
Block a user