Some basic tests

This commit is contained in:
Adam Matthiesen 2024-03-03 07:06:58 -08:00
parent 8db7190be8
commit fd21ee8df5
10 changed files with 440 additions and 8 deletions

View File

@ -12,14 +12,14 @@
"lint:fix": "biome check --apply .",
"ci:version": "pnpm changeset version",
"ci:publish": "pnpm changeset publish",
"ci:test:api": "pnpm --filter astro-ghostcms test:ci",
"test:api": "pnpm --filter astro-ghostcms test",
"test:api:watch": "pnpm --filter astro-ghostcms test:watch",
"test:api:coverage": "pnpm --filter astro-ghostcms test:coverage",
"ci:test:integration": "pnpm --filter astro-ghostcms test:ci",
"test:integration": "pnpm --filter astro-ghostcms test",
"test:integration:watch": "pnpm --filter astro-ghostcms test:watch",
"test:integration:coverage": "pnpm --filter astro-ghostcms test:coverage",
"test:create": "pnpm --filter create-astro-ghostcms test",
"test:slg": "pnpm --filter starlight-ghostcms test",
"test:slg:watch": "pnpm --filter starlight-ghostcms test:watch",
"test:slg:coverage": "pnpm --filter starlight-ghostcms test:coverage"
"test:starlight": "pnpm --filter starlight-ghostcms test",
"test:starlight:watch": "pnpm --filter starlight-ghostcms test:watch",
"test:starlight:coverage": "pnpm --filter starlight-ghostcms test:coverage"
},
"devDependencies": {
"@biomejs/biome": "1.5.3",

View File

@ -49,11 +49,17 @@
"./open-graph/*": "./src/integrations/satoriog/routes/*"
},
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"test:ci": "vitest run --coverage.enabled --coverage.reporter='text-summary'"
},
"peerDependencies": {
"astro": "^4.4.1"
},
"devDependencies": {
"vitest": "^1.3.0",
"vitest-fetch-mock": "^0.2.2"
},
"dependencies": {
"@astrojs/rss": "^4.0.5",

View File

@ -0,0 +1,84 @@
import { assert, beforeEach, describe, expect, test } from "vitest";
import { TSGhostContentAPI } from "@ts-ghost/content-api";
describe("content-api", () => {
let api: TSGhostContentAPI;
beforeEach(() => {
api = new TSGhostContentAPI("https://ghost.org", "59d4bf56c73c04a18c867dc3ba", "v5.0");
});
test("content-api", () => {
expect(api).toBeDefined();
});
test("content-api shouldn't instantiate with an incorrect url", () => {
assert.throws(() => {
const api = new TSGhostContentAPI("ghost.org", "59d4bf56c73c04a18c867dc3ba", "v5.0");
api.settings;
});
});
test("content-api shouldn't instantiate with an incorrect key", () => {
assert.throws(() => {
const api = new TSGhostContentAPI("https://ghost.org", "a", "v5.0");
api.settings;
});
});
test("content-api shouldn't instantiate with an incorrect version", () => {
assert.throws(() => {
const api = new TSGhostContentAPI(
"https://ghost.org",
"1efedd9db174adee2d23d982:4b74dca0219bad629852191af326a45037346c2231240e0f7aec1f9371cc14e8",
// @ts-expect-error
"v4.0",
);
api.settings;
});
});
test("content-api.posts", () => {
expect(api.posts).toBeDefined();
expect(api.posts.browse).toBeDefined();
expect(api.posts.read).toBeDefined();
});
test("content-api.pages", () => {
expect(api.pages).toBeDefined();
expect(api.pages.browse).toBeDefined();
expect(api.pages.read).toBeDefined();
});
test("content-api.tags", () => {
expect(api.tags).toBeDefined();
expect(api.tags.browse).toBeDefined();
expect(api.tags.read).toBeDefined();
});
test("content-api.tiers", () => {
expect(api.tiers).toBeDefined();
expect(api.tiers.browse).toBeDefined();
expect(api.tiers.read).toBeDefined();
});
test("content-api.authors", () => {
expect(api.authors).toBeDefined();
expect(api.authors.browse).toBeDefined();
expect(api.authors.read).toBeDefined();
// @ts-expect-error
expect(api.authors.add).toBeUndefined();
// @ts-expect-error
expect(api.authors.edit).toBeUndefined();
expect(api.authors).toBeDefined();
});
test("content-api.settings", () => {
expect(api.settings).toBeDefined();
expect(api.settings.fetch).toBeDefined();
// @ts-expect-error
expect(api.settings.read).toBeUndefined();
// @ts-expect-error
expect(api.settings.browse).toBeUndefined();
});
});

View File

@ -0,0 +1,32 @@
import { describe, expect, it } from "vitest";
// Modified version of invariant script to allow tests
const isProduction = false;
const prefix: string = "Invariant failed";
function invariant(condition: any, message?: string | (() => string)) {
if (condition) {
return;
}
if (isProduction) {
throw new Error(prefix);
}
const provided: string | undefined =
typeof message === "function" ? message() : message;
const value: string = provided ? `${prefix}: ${provided}` : prefix;
return value;
}
// TEST SECTION
const testTrue = true;
const testFalse = false;
describe("test invariant", () => {
it("Test `true` value", () => {
invariant(testTrue, "This should not error");
expect(null);
});
it("Test `false` value", () => {
invariant(testFalse, "This should Error");
expect(String("Invariant failed"));
});
});

View File

@ -0,0 +1,146 @@
import createFetchMock from "vitest-fetch-mock";
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import { TSGhostContentAPI } from "@ts-ghost/content-api";
const fetchMocker = createFetchMock(vi);
describe("authors api .browse() Args Type-safety", () => {
const url = process.env.VITE_GHOST_URL || "https://my-ghost-blog.com";
const key = process.env.VITE_GHOST_CONTENT_API_KEY || "59d4bf56c73c04a18c867dc3ba";
const api = new TSGhostContentAPI(url, key, "v5.0");
test(".browse() params shouldnt accept invalid params", () => {
// @ts-expect-error - shouldnt accept invalid params
const browse = api.authors.browse({ pp: 2 });
expect(browse.getParams().browseParams).toStrictEqual({});
});
test(".browse() 'order' params should ony accept fields values", () => {
// @ts-expect-error - order should ony contain field
expect(() => api.authors.browse({ order: "foo ASC" })).toThrow();
// valid
expect(api.authors.browse({ order: "name ASC" }).getParams().browseParams).toStrictEqual({
order: "name ASC",
});
expect(api.authors.browse({ order: "name ASC,slug DESC" }).getParams().browseParams).toStrictEqual({
order: "name ASC,slug DESC",
});
expect(
api.authors.browse({ order: "name ASC,slug DESC,location ASC" }).getParams().browseParams
).toStrictEqual({
order: "name ASC,slug DESC,location ASC",
});
// @ts-expect-error - order should ony contain field (There is a typo in location)
expect(() => api.authors.browse({ order: "name ASC,slug DESC,locaton ASC" })).toThrow();
});
test(".browse() 'filter' params should ony accept valid field", () => {
expect(() =>
api.authors.browse({
// @ts-expect-error - order should ony contain field
filter: "foo:bar",
})
).toThrow();
expect(
api.authors
.browse({
filter: "name:bar",
})
.getParams().browseParams
).toStrictEqual({
filter: "name:bar",
});
expect(
api.authors
.browse({
filter: "name:bar+slug:-test",
})
.getParams().browseParams
).toStrictEqual({
filter: "name:bar+slug:-test",
});
});
test(".browse 'fields' argument should ony accept valid fields", () => {
expect(
api.authors
.browse()
.fields({
// @ts-expect-error - order should ony contain field
foo: true,
})
.getOutputFields()
).toEqual([]);
expect(api.authors.browse().fields({ location: true }).getOutputFields()).toEqual(["location"]);
expect(api.authors.browse().fields({ name: true, website: true }).getOutputFields()).toEqual([
"name",
"website",
]);
});
});
describe("authors resource mocked", () => {
let api: TSGhostContentAPI;
beforeEach(() => {
api = new TSGhostContentAPI("https://my-ghost-blog.com", "59d4bf56c73c04a18c867dc3ba", "v5.0");
fetchMocker.enableMocks();
});
afterEach(() => {
vi.restoreAllMocks();
});
test("aouthors should be fetched correctly", async () => {
const authors = api.authors;
expect(authors).not.toBeUndefined();
const browseQuery = authors
.browse({
page: 2,
})
.fields({
name: true,
id: true,
});
expect(browseQuery).not.toBeUndefined();
expect(browseQuery.getOutputFields()).toStrictEqual(["name", "id"]);
fetchMocker.doMockOnce(
JSON.stringify({
authors: [
{
name: "foo",
id: "eaoizdjoa1321123",
},
],
meta: {
pagination: {
page: 1,
limit: 15,
pages: 1,
total: 1,
next: null,
prev: null,
},
},
})
);
const result = await browseQuery.fetch();
expect(fetchMocker).toHaveBeenCalledTimes(1);
expect(fetchMocker).toHaveBeenCalledWith(
"https://my-ghost-blog.com/ghost/api/content/authors/?page=2&fields=name%2Cid&key=59d4bf56c73c04a18c867dc3ba",
{
headers: {
"Content-Type": "application/json",
"Accept-Version": "v5.0",
},
}
);
expect(result).not.toBeUndefined();
if (result.success) {
expect(result.data.length).toBe(1);
expect(result.data[0].name).toBe("foo");
expect(result.data[0].id).toBe("eaoizdjoa1321123");
}
});
});

View File

@ -0,0 +1,55 @@
import { describe, expect, test } from "vitest";
import { TSGhostContentAPI } from "@ts-ghost/content-api";
import type { Post } from "./index";
const url = process.env.VITE_GHOST_URL || "https://my-ghost-blog.com";
const key = process.env.VITE_GHOST_CONTENT_API_KEY || "59d4bf56c73c04a18c867dc3ba";
describe("posts api .browse() Args Type-safety", () => {
const api = new TSGhostContentAPI(url, key, "v5.0");
test(".browse() params shouldnt accept invalid params", () => {
// @ts-expect-error - shouldnt accept invalid params
const browse = api.posts.browse({ pp: 2 });
expect(browse.getParams().browseParams).toStrictEqual({});
const outputFields = {
slug: true,
title: true,
// @ts-expect-error - shouldnt accept invalid params
foo: true,
} satisfies { [k in keyof Post]?: true | undefined };
const test = api.posts
.browse()
// @ts-expect-error - shouldnt accept invalid params
.fields(outputFields);
expect(test.getOutputFields()).toEqual(["slug", "title"]);
const fields = ["slug", "title", "foo"] as const;
const unknownOriginFields = fields.reduce((acc, k) => {
acc[k as keyof Post] = true;
return acc;
}, {} as { [k in keyof Post]?: true | undefined });
const result = api.posts.browse().fields(unknownOriginFields);
expect(result.getOutputFields()).toEqual(["slug", "title"]);
});
test(".browse() params, output fields declare const", () => {
const outputFields = {
slug: true,
title: true,
} satisfies { [k in keyof Post]?: true | undefined };
const test = api.posts.browse().fields(outputFields);
expect(test.getOutputFields()).toEqual(["slug", "title"]);
// @ts-expect-error - shouldnt accept invalid params
expect(() => api.posts.browse({ filter: "slugg:test" })).toThrow();
// @ts-expect-error - shouldnt accept invalid params
expect(() => api.posts.browse({ filter: "slug:test,foo:-[bar,baz]" })).toThrow();
expect(api.posts.browse({ filter: "slug:test,tags:-[bar,baz]" })).toBeDefined();
expect(api.posts.browse({ filter: "slug:test,tags:[bar,baz]" })).toBeDefined();
// @ts-expect-error - shouldnt accept invalid params
expect(() => api.posts.browse({ filter: "slug:test,food:-[bar,baz]" })).toThrow();
});
});

View File

@ -0,0 +1,36 @@
import { beforeEach, describe, expect, test } from "vitest";
import { TSGhostContentAPI } from "@ts-ghost/content-api";
const url = process.env.VITE_GHOST_URL || "https://my-ghost-blog.com";
const key = process.env.VITE_GHOST_CONTENT_API_KEY || "59d4bf56c73c04a18c867dc3ba";
describe("settings integration tests browse", () => {
let api: TSGhostContentAPI;
beforeEach(() => {
api = new TSGhostContentAPI(url, key, "v5.0");
});
test("settings.fetch()", async () => {
const result = await api.settings.fetch();
expect(result).not.toBeUndefined();
expect(result).not.toBeNull();
if (!result.success) {
expect(result.errors).toBeDefined();
expect(result.errors).toHaveLength(1);
} else {
expect(result.data).toBeDefined();
const settings = result.data;
expect(settings).toBeDefined();
expect(settings.title).toBe("Astro Starter");
expect(settings.description).toBe("Thoughts, stories and ideas.");
expect(settings.logo).toBeNull();
expect(settings.cover_image).toBe("https://static.ghost.org/v4.0.0/images/publication-cover.jpg");
expect(settings.icon).toBeNull();
expect(settings.lang).toBe("en");
expect(settings.timezone).toBe("Etc/UTC");
expect(settings.codeinjection_head).toBeNull();
expect(settings.codeinjection_foot).toBeNull();
expect(settings.members_support_address).toBe("noreply");
}
});
});

View File

@ -0,0 +1,54 @@
import { expect, describe, it } from "vitest";
import { GhostUserConfigSchema } from "./userconfig";
describe("GhostUserConfigSchema", () => {
it("should validate a valid user config", () => {
const validConfig = {
ghostURL: "https://ghostdemo.matthiesen.xyz",
disableThemeProvider: true,
ThemeProvider: {
theme: "@matthiesenxyz/astro-ghostcms-theme-default",
},
disableDefault404: false,
enableRSSFeed: true,
enableOGImages: true,
sitemap: {
// sitemap configuration
},
robotstxt: {
// robotstxt configuration
},
fullConsoleLogs: false,
};
const result = GhostUserConfigSchema.safeParse(validConfig);
expect(result.success).to.be.true;
expect(result.data).to.deep.equal(validConfig);
});
it("should invalidate an invalid user config", () => {
const invalidConfig = {
ghostURL: "invalid-url",
disableThemeProvider: "true",
ThemeProvider: {
theme: 123,
},
disableDefault404: "false",
enableRSSFeed: "true",
enableOGImages: "true",
sitemap: {
// invalid sitemap configuration
},
robotstxt: {
// invalid robotstxt configuration
},
fullConsoleLogs: "false",
};
const result = GhostUserConfigSchema.safeParse(invalidConfig);
expect(result.success).to.be.false;
expect(!result.success).to.exist;
});
});

View File

@ -0,0 +1,12 @@
/// <reference types="vitest" />
/// <reference types="vite/client" />
import { defineProject } from 'vitest/config'
export default defineProject({
test: {
globals: true,
include: ["./**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
watchExclude: [".*\\/node_modules\\/.*", ".*\\/build\\/.*"],
exclude: ["node_modules", "dist", ".idea", ".git", ".cache"],
},
})

View File

@ -65,6 +65,13 @@ importers:
vite:
specifier: ^5.1.4
version: 5.1.4(@types/node@20.11.19)
devDependencies:
vitest:
specifier: ^1.3.0
version: 1.3.0(@types/node@20.11.19)(@vitest/ui@1.3.0)
vitest-fetch-mock:
specifier: ^0.2.2
version: 0.2.2(vitest@1.3.0)
packages/astro-ghostcms-brutalbyelian:
dependencies:
@ -8809,7 +8816,7 @@ packages:
strip-literal: 2.0.0
tinybench: 2.6.0
tinypool: 0.8.2
vite: 5.1.3(@types/node@20.11.19)
vite: 5.1.4(@types/node@20.11.19)
vite-node: 1.3.0(@types/node@20.11.19)
why-is-node-running: 2.2.2
transitivePeerDependencies: