mirror of
https://github.com/LukeHagar/unicorn-utterances.git
synced 2025-12-06 04:21:55 +00:00
chore: fix tests, add new SEO tests
This commit is contained in:
@@ -15,6 +15,9 @@ export const MockPost: PostInfo & RenderedPostInfo = {
|
||||
headingsWithId: [],
|
||||
wordCount: 10000,
|
||||
content: "",
|
||||
translations: {
|
||||
en: "English",
|
||||
},
|
||||
};
|
||||
|
||||
export const MockMultiAuthorPost: PostInfo & RenderedPostInfo = {
|
||||
@@ -31,4 +34,46 @@ export const MockMultiAuthorPost: PostInfo & RenderedPostInfo = {
|
||||
headingsWithId: [],
|
||||
wordCount: 100000,
|
||||
content: "",
|
||||
translations: {
|
||||
en: "English",
|
||||
},
|
||||
};
|
||||
|
||||
export const MockMuliLanguagePost: PostInfo & RenderedPostInfo = {
|
||||
excerpt:
|
||||
"This would be a second auto generated excerpt of the post in particular",
|
||||
title: "Another post title",
|
||||
published: "10-20-2010",
|
||||
tags: ["item1"],
|
||||
description:
|
||||
"This is another short description dunno why this would be this short",
|
||||
authors: [MockUnicornTwo, MockUnicorn],
|
||||
license: MockLicense,
|
||||
slug: "this-other-post-name-here",
|
||||
headingsWithId: [],
|
||||
wordCount: 100000,
|
||||
content: "",
|
||||
translations: {
|
||||
es: "Español",
|
||||
},
|
||||
};
|
||||
|
||||
export const MockCanonicalPost: PostInfo & RenderedPostInfo = {
|
||||
excerpt:
|
||||
"This would be a second auto generated excerpt of the post in particular",
|
||||
title: "Another post title",
|
||||
published: "10-20-2010",
|
||||
originalLink: "https://google.com/",
|
||||
tags: ["item1"],
|
||||
description:
|
||||
"This is another short description dunno why this would be this short",
|
||||
authors: [MockUnicornTwo, MockUnicorn],
|
||||
license: MockLicense,
|
||||
slug: "this-other-post-name-here",
|
||||
headingsWithId: [],
|
||||
wordCount: 100000,
|
||||
content: "",
|
||||
translations: {
|
||||
en: "English",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -206,7 +206,7 @@ export const SEO: React.FC<PropsWithChildren<SEOProps>> = (props) => {
|
||||
rel="alternate"
|
||||
href={
|
||||
siteMetadata.siteUrl +
|
||||
`${lang === "en" ? "" : "/"}${lang}` +
|
||||
`${lang === "en" ? "" : "/"}${lang === "en" ? "" : lang}` +
|
||||
removePrefixLanguageFromPath(pathName || "")
|
||||
}
|
||||
hrefLang={lang}
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import React from "react";
|
||||
import React, { PropsWithChildren } from "react";
|
||||
import { fireEvent, render } from "@testing-library/react";
|
||||
import { siteMetadata } from "__mocks__/data/mock-site-metadata";
|
||||
import { MockMultiAuthorPost, MockPost } from "__mocks__/data/mock-post";
|
||||
import {
|
||||
MockCanonicalPost,
|
||||
MockMuliLanguagePost,
|
||||
MockMultiAuthorPost,
|
||||
MockPost,
|
||||
} from "__mocks__/data/mock-post";
|
||||
import BlogPostTemplate from "../../pages/[...postInfo]";
|
||||
import ReactDOMServer from "react-dom/server";
|
||||
import { RouterContext } from "next/dist/shared/lib/router-context";
|
||||
import { Languages } from "types/index";
|
||||
import { getAllByRel, getByProperty, getByRel } from "utils/tests";
|
||||
// import { axe } from "jest-axe";
|
||||
|
||||
const getElement = ({
|
||||
@@ -14,6 +19,7 @@ const getElement = ({
|
||||
slug = "/slug",
|
||||
postsDirectory = "/posts",
|
||||
seriesPosts = [],
|
||||
lang = "en",
|
||||
}: {
|
||||
post: any;
|
||||
fn?: () => void;
|
||||
@@ -21,6 +27,7 @@ const getElement = ({
|
||||
slug?: string;
|
||||
postsDirectory?: string;
|
||||
seriesPosts?: any[];
|
||||
lang?: Languages;
|
||||
}) => (
|
||||
<RouterContext.Provider
|
||||
value={
|
||||
@@ -39,6 +46,7 @@ const getElement = ({
|
||||
post={post}
|
||||
markdownHTML={markdownHTML}
|
||||
slug={slug}
|
||||
lang={lang}
|
||||
postsDirectory={postsDirectory}
|
||||
seriesPosts={seriesPosts}
|
||||
suggestedPosts={[]}
|
||||
@@ -74,7 +82,7 @@ test("Blog post page renders", async () => {
|
||||
expect(navigatePushFn).toHaveBeenCalledTimes(2);
|
||||
|
||||
// Renders the post body properly
|
||||
expect((await findByTestId("post-body-div")).innerHTML).toBe(
|
||||
expect((await findByTestId("post-body-div")).innerHTML).toContain(
|
||||
"<div>Hey there</div>"
|
||||
);
|
||||
});
|
||||
@@ -112,12 +120,71 @@ test("Blog post page handles two authors", async () => {
|
||||
expect(navigatePushFn).toHaveBeenCalledTimes(4);
|
||||
|
||||
// Renders the post body properly
|
||||
expect((await findByTestId("post-body-div")).innerHTML).toBe(
|
||||
expect((await findByTestId("post-body-div")).innerHTML).toContain(
|
||||
"<div>Hello, friends</div>"
|
||||
);
|
||||
});
|
||||
|
||||
test.todo("SEO should apply");
|
||||
/**
|
||||
* Next head mocking to `head` element
|
||||
*
|
||||
* TODO: Turn this + queries into a library
|
||||
*/
|
||||
const mockDocument = document;
|
||||
|
||||
jest.mock("next/head", () => {
|
||||
const ReactDOM = require("react-dom");
|
||||
return ({ children }: PropsWithChildren<unknown>) => {
|
||||
return ReactDOM.createPortal(children, mockDocument.head);
|
||||
};
|
||||
});
|
||||
|
||||
test("SEO should show translation data", () => {
|
||||
const navigatePushFn = jest.fn();
|
||||
|
||||
render(
|
||||
getElement({
|
||||
post: MockMuliLanguagePost,
|
||||
fn: navigatePushFn,
|
||||
markdownHTML: "Hello, friends",
|
||||
lang: "en",
|
||||
})
|
||||
);
|
||||
|
||||
const alts = getAllByRel(document.head, "alternate");
|
||||
expect(alts.length).toBe(2);
|
||||
expect(alts[0]).toHaveProperty("hreflang", "x-default");
|
||||
expect(alts[1]).toHaveProperty("hreflang", "es");
|
||||
|
||||
expect(getByProperty(document.head, "og:locale")).toHaveProperty(
|
||||
"content",
|
||||
"en"
|
||||
);
|
||||
expect(getByProperty(document.head, "og:locale:alternate")).toHaveProperty(
|
||||
"content",
|
||||
"es"
|
||||
);
|
||||
});
|
||||
|
||||
test("Canonical tags should show in SEO", () => {
|
||||
const navigatePushFn = jest.fn();
|
||||
|
||||
render(
|
||||
getElement({
|
||||
post: MockCanonicalPost,
|
||||
fn: navigatePushFn,
|
||||
markdownHTML: "Hello, friends",
|
||||
lang: "en",
|
||||
})
|
||||
);
|
||||
|
||||
expect(getByRel(document.head, "canonical")).toHaveProperty(
|
||||
"href",
|
||||
"https://google.com/"
|
||||
);
|
||||
});
|
||||
|
||||
test.todo("SEO Twitter");
|
||||
test.todo("Shows post footer image");
|
||||
|
||||
// test.skip("Blog post page should not have axe errors", async () => {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { RenderedPostInfo } from "types/PostInfo";
|
||||
import { SlugPostInfo } from "constants/queries";
|
||||
import Link from "next/link";
|
||||
import { Languages } from "types/index";
|
||||
import { Fragment } from "react";
|
||||
|
||||
interface TranslationsHeaderProps {
|
||||
post: SlugPostInfo & RenderedPostInfo;
|
||||
@@ -14,12 +15,12 @@ export const TranslationsHeader = ({ post }: TranslationsHeaderProps) => {
|
||||
{(Object.keys(post.translations) as Languages[]).map((lang, i, arr) => {
|
||||
const langHref = lang === "en" ? "" : `${lang}/`;
|
||||
return (
|
||||
<>
|
||||
<Link key={lang} passHref href={`/${langHref}posts/${post.slug}`}>
|
||||
<Fragment key={lang}>
|
||||
<Link passHref href={`/${langHref}posts/${post.slug}`}>
|
||||
<a>{post.translations[lang]}</a>
|
||||
</Link>
|
||||
{i !== arr.length - 1 && <span>, </span>}
|
||||
</>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
@@ -24,7 +24,7 @@ import { MailingList } from "components/mailing-list";
|
||||
|
||||
import GitHubIcon from "assets/icons/github.svg";
|
||||
import CommentsIcon from "assets/icons/message.svg";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { useContext, useEffect, useMemo, useState } from "react";
|
||||
import { ThemeContext } from "constants/theme-context";
|
||||
import { siteMetadata } from "constants/site-config";
|
||||
import "react-medium-image-zoom/dist/styles.css";
|
||||
@@ -101,6 +101,19 @@ const Post = ({
|
||||
|
||||
const GHLink = `https://github.com/${siteMetadata.repoPath}/tree/master${siteMetadata.relativeToPosts}/${slug}/index.md`;
|
||||
|
||||
const langData = useMemo(() => {
|
||||
const otherLangs = post.translations
|
||||
? (Object.keys(post.translations).filter(
|
||||
(t) => t !== lang
|
||||
) as Languages[])
|
||||
: [];
|
||||
|
||||
return {
|
||||
otherLangs,
|
||||
currentLang: lang,
|
||||
};
|
||||
}, [lang, post.translations]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SEO
|
||||
@@ -113,12 +126,7 @@ const Post = ({
|
||||
type="article"
|
||||
pathName={router.asPath}
|
||||
canonical={post.originalLink}
|
||||
langData={{
|
||||
currentLang: lang,
|
||||
otherLangs: post.translations
|
||||
? (Object.keys(post.translations) as Languages[])
|
||||
: [],
|
||||
}}
|
||||
langData={langData}
|
||||
/>
|
||||
<article>
|
||||
<BlogPostLayout
|
||||
|
||||
@@ -18,7 +18,7 @@ export interface PostInfo {
|
||||
originalLink?: string;
|
||||
content: string;
|
||||
tags: string[];
|
||||
translations: Record<Languages, string>;
|
||||
translations: Partial<Record<Languages, string>>;
|
||||
}
|
||||
|
||||
export interface RenderedPostInfo {
|
||||
|
||||
3
src/utils/queries/index.ts
Normal file
3
src/utils/queries/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from "./name";
|
||||
export * from "./property";
|
||||
export * from "./rel";
|
||||
34
src/utils/queries/name.ts
Normal file
34
src/utils/queries/name.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { wrapAllByQueryWithSuggestion } from "@testing-library/dom/dist/query-helpers";
|
||||
import { checkContainerType } from "@testing-library/dom/dist/helpers";
|
||||
import {
|
||||
AllByBoundAttribute,
|
||||
GetErrorFunction,
|
||||
queryAllByAttribute,
|
||||
buildQueries,
|
||||
} from "@testing-library/dom";
|
||||
|
||||
const queryAllByName: AllByBoundAttribute = (...args) => {
|
||||
checkContainerType(args[0]);
|
||||
return queryAllByAttribute("name", ...args);
|
||||
};
|
||||
const getMultipleError: GetErrorFunction<[unknown]> = (c, text) =>
|
||||
`Found multiple elements with the name text of: ${text}`;
|
||||
const getMissingError: GetErrorFunction<[unknown]> = (c, text) =>
|
||||
`Unable to find an element with the name text of: ${text}`;
|
||||
|
||||
const queryAllByNameWithSuggestions = wrapAllByQueryWithSuggestion<
|
||||
// @ts-expect-error -- See `wrapAllByQueryWithSuggestion` Argument constraint comment
|
||||
[name: Matcher, options?: MatcherOptions]
|
||||
>(queryAllByName, queryAllByName.name, "queryAll");
|
||||
|
||||
const [queryByName, getAllByName, getByName, findAllByName, findByName] =
|
||||
buildQueries(queryAllByName, getMultipleError, getMissingError);
|
||||
|
||||
export {
|
||||
queryByName,
|
||||
queryAllByNameWithSuggestions as queryAllByName,
|
||||
getByName,
|
||||
getAllByName,
|
||||
findAllByName,
|
||||
findByName,
|
||||
};
|
||||
39
src/utils/queries/property.ts
Normal file
39
src/utils/queries/property.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { wrapAllByQueryWithSuggestion } from "@testing-library/dom/dist/query-helpers";
|
||||
import { checkContainerType } from "@testing-library/dom/dist/helpers";
|
||||
import {
|
||||
AllByBoundAttribute,
|
||||
GetErrorFunction,
|
||||
queryAllByAttribute,
|
||||
buildQueries,
|
||||
} from "@testing-library/dom";
|
||||
|
||||
const queryAllByProperty: AllByBoundAttribute = (...args) => {
|
||||
checkContainerType(args[0]);
|
||||
return queryAllByAttribute("property", ...args);
|
||||
};
|
||||
const getMultipleError: GetErrorFunction<[unknown]> = (c, text) =>
|
||||
`Found multiple elements with the property text of: ${text}`;
|
||||
const getMissingError: GetErrorFunction<[unknown]> = (c, text) =>
|
||||
`Unable to find an element with the property text of: ${text}`;
|
||||
|
||||
const queryAllByPropertyWithSuggestions = wrapAllByQueryWithSuggestion<
|
||||
// @ts-expect-error -- See `wrapAllByQueryWithSuggestion` Argument constraint comment
|
||||
[property: Matcher, options?: MatcherOptions]
|
||||
>(queryAllByProperty, queryAllByProperty.name, "queryAll");
|
||||
|
||||
const [
|
||||
queryByProperty,
|
||||
getAllByProperty,
|
||||
getByProperty,
|
||||
findAllByProperty,
|
||||
findByProperty,
|
||||
] = buildQueries(queryAllByProperty, getMultipleError, getMissingError);
|
||||
|
||||
export {
|
||||
queryByProperty,
|
||||
queryAllByPropertyWithSuggestions as queryAllByProperty,
|
||||
getByProperty,
|
||||
getAllByProperty,
|
||||
findAllByProperty,
|
||||
findByProperty,
|
||||
};
|
||||
36
src/utils/queries/rel.ts
Normal file
36
src/utils/queries/rel.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { wrapAllByQueryWithSuggestion } from "@testing-library/dom/dist/query-helpers";
|
||||
import { checkContainerType } from "@testing-library/dom/dist/helpers";
|
||||
import {
|
||||
AllByBoundAttribute,
|
||||
GetErrorFunction,
|
||||
queryAllByAttribute,
|
||||
buildQueries,
|
||||
Matcher,
|
||||
MatcherOptions,
|
||||
} from "@testing-library/dom";
|
||||
|
||||
const queryAllByRel: AllByBoundAttribute = (...args) => {
|
||||
checkContainerType(args[0]);
|
||||
return queryAllByAttribute("rel", ...args);
|
||||
};
|
||||
const getMultipleError: GetErrorFunction<[unknown]> = (c, text) =>
|
||||
`Found multiple elements with the rel text of: ${text}`;
|
||||
const getMissingError: GetErrorFunction<[unknown]> = (c, text) =>
|
||||
`Unable to find an element with the rel text of: ${text}`;
|
||||
|
||||
const queryAllByRelWithSuggestions = wrapAllByQueryWithSuggestion<
|
||||
// @ts-expect-error -- See `wrapAllByQueryWithSuggestion` Argument constraint comment
|
||||
[rel: Matcher, options?: MatcherOptions]
|
||||
>(queryAllByRel, queryAllByRel.name, "queryAll");
|
||||
|
||||
const [queryByRel, getAllByRel, getByRel, findAllByRel, findByRel] =
|
||||
buildQueries(queryAllByRel, getMultipleError, getMissingError);
|
||||
|
||||
export {
|
||||
queryByRel,
|
||||
queryAllByRelWithSuggestions as queryAllByRel,
|
||||
getByRel,
|
||||
getAllByRel,
|
||||
findAllByRel,
|
||||
findByRel,
|
||||
};
|
||||
17
src/utils/queries/types.d.ts
vendored
Normal file
17
src/utils/queries/types.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
declare module "@testing-library/dom/dist/query-helpers" {
|
||||
import { Variant, WithSuggest } from "@testing-library/dom";
|
||||
declare const wrapAllByQueryWithSuggestion: <
|
||||
Arguments extends [...unknown[], WithSuggest]
|
||||
>(
|
||||
query: (container: HTMLElement, ...args: Arguments) => HTMLElement[],
|
||||
queryAllByName: string,
|
||||
variant: Variant
|
||||
) => (container: HTMLElement, ...args: Arguments) => HTMLElement[];
|
||||
|
||||
export { wrapAllByQueryWithSuggestion };
|
||||
}
|
||||
|
||||
declare module "@testing-library/dom/dist/helpers" {
|
||||
declare const checkContainerType: (...props: any[]) => any;
|
||||
export { checkContainerType };
|
||||
}
|
||||
2
src/utils/tests.ts
Normal file
2
src/utils/tests.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
// TODO: Turn this into a library
|
||||
export * from "./queries";
|
||||
Reference in New Issue
Block a user