diff --git a/package.json b/package.json
index 04ff3b53..1b518edb 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/packages/astro-ghostcms/package.json b/packages/astro-ghostcms/package.json
index c81e3c24..64518bcb 100644
--- a/packages/astro-ghostcms/package.json
+++ b/packages/astro-ghostcms/package.json
@@ -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",
diff --git a/packages/astro-ghostcms/src/api/ghostAPI.test.ts b/packages/astro-ghostcms/src/api/ghostAPI.test.ts
new file mode 100644
index 00000000..c4599fc1
--- /dev/null
+++ b/packages/astro-ghostcms/src/api/ghostAPI.test.ts
@@ -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();
+ });
+});
diff --git a/packages/astro-ghostcms/src/api/invariant.test.ts b/packages/astro-ghostcms/src/api/invariant.test.ts
new file mode 100644
index 00000000..20c887b9
--- /dev/null
+++ b/packages/astro-ghostcms/src/api/invariant.test.ts
@@ -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"));
+ });
+});
diff --git a/packages/astro-ghostcms/src/schemas/api/authors.test.ts b/packages/astro-ghostcms/src/schemas/api/authors.test.ts
new file mode 100644
index 00000000..9cee527b
--- /dev/null
+++ b/packages/astro-ghostcms/src/schemas/api/authors.test.ts
@@ -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");
+ }
+ });
+});
\ No newline at end of file
diff --git a/packages/astro-ghostcms/src/schemas/api/posts.test.ts b/packages/astro-ghostcms/src/schemas/api/posts.test.ts
new file mode 100644
index 00000000..f682720b
--- /dev/null
+++ b/packages/astro-ghostcms/src/schemas/api/posts.test.ts
@@ -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();
+ });
+});
\ No newline at end of file
diff --git a/packages/astro-ghostcms/src/schemas/api/settings.test.ts b/packages/astro-ghostcms/src/schemas/api/settings.test.ts
new file mode 100644
index 00000000..e5da99da
--- /dev/null
+++ b/packages/astro-ghostcms/src/schemas/api/settings.test.ts
@@ -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");
+ }
+ });
+});
\ No newline at end of file
diff --git a/packages/astro-ghostcms/src/schemas/userconfig.test.ts b/packages/astro-ghostcms/src/schemas/userconfig.test.ts
new file mode 100644
index 00000000..8c1274bb
--- /dev/null
+++ b/packages/astro-ghostcms/src/schemas/userconfig.test.ts
@@ -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;
+ });
+});
\ No newline at end of file
diff --git a/packages/astro-ghostcms/vitest.config.ts b/packages/astro-ghostcms/vitest.config.ts
new file mode 100644
index 00000000..f19e0d02
--- /dev/null
+++ b/packages/astro-ghostcms/vitest.config.ts
@@ -0,0 +1,12 @@
+///
+///
+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"],
+ },
+})
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4965dfb2..3546f039 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -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: