diff --git a/.eslintrc.json b/.eslintrc.json index a9ba90d..b8f393c 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -12,7 +12,7 @@ }, "plugins": ["@typescript-eslint"], "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"], - "ignorePatterns": ["dist", "node_modules"], + "ignorePatterns": ["dist", "node_modules", "types/*.d.ts", "src/components/scripts/*.inline.ts"], "rules": { "@typescript-eslint/no-unused-vars": [ "error", diff --git a/src/components/Search.tsx b/src/components/Search.tsx index 09b4ac4..97a59e2 100644 --- a/src/components/Search.tsx +++ b/src/components/Search.tsx @@ -6,7 +6,7 @@ import type { import { classNames } from "../util/lang"; import { i18n } from "../i18n"; import style from "./styles/search.scss"; -// @ts-ignore +// @ts-expect-error - inline script imported as string by esbuild loader import script from "./scripts/search.inline.ts"; export interface SearchOptions { diff --git a/src/components/styles/search.scss b/src/components/styles/search.scss index 6a5f633..b859a50 100644 --- a/src/components/styles/search.scss +++ b/src/components/styles/search.scss @@ -78,7 +78,9 @@ width: 100%; border-radius: 7px; background: var(--light); - box-shadow: 0 14px 50px rgba(27, 33, 48, 0.12), 0 10px 30px rgba(27, 33, 48, 0.16); + box-shadow: + 0 14px 50px rgba(27, 33, 48, 0.12), + 0 10px 30px rgba(27, 33, 48, 0.16); margin-bottom: 2em; } @@ -112,7 +114,12 @@ } @media all and not (max-width: 800px) { - .search > .search-container > .search-space > .search-layout[data-preview] .result-card > p.preview { + .search + > .search-container + > .search-space + > .search-layout[data-preview] + .result-card + > p.preview { display: none; } @@ -201,30 +208,72 @@ color: var(--dark); } -.search > .search-container > .search-space > .search-layout > .results-container .result-card:hover, -.search > .search-container > .search-space > .search-layout > .results-container .result-card:focus, -.search > .search-container > .search-space > .search-layout > .results-container .result-card.focus { +.search + > .search-container + > .search-space + > .search-layout + > .results-container + .result-card:hover, +.search + > .search-container + > .search-space + > .search-layout + > .results-container + .result-card:focus, +.search + > .search-container + > .search-space + > .search-layout + > .results-container + .result-card.focus { background: var(--lightgray); } -.search > .search-container > .search-space > .search-layout > .results-container .result-card > h3 { +.search + > .search-container + > .search-space + > .search-layout + > .results-container + .result-card + > h3 { margin: 0; color: var(--secondary); } -.search > .search-container > .search-space > .search-layout > .results-container .result-card > ul.tags { +.search + > .search-container + > .search-space + > .search-layout + > .results-container + .result-card + > ul.tags { margin-top: 0.45rem; margin-bottom: 0; padding: 0; list-style: none; } -.search > .search-container > .search-space > .search-layout > .results-container .result-card > ul.tags > li { +.search + > .search-container + > .search-space + > .search-layout + > .results-container + .result-card + > ul.tags + > li { display: inline-block; margin-right: 0.3rem; } -.search > .search-container > .search-space > .search-layout > .results-container .result-card > ul > li > p { +.search + > .search-container + > .search-space + > .search-layout + > .results-container + .result-card + > ul + > li + > p { border-radius: 8px; background-color: var(--highlight); padding: 0.2rem 0.4rem; @@ -235,7 +284,15 @@ font-size: 0.85rem; } -.search > .search-container > .search-space > .search-layout > .results-container .result-card > ul > li > p.match-tag { +.search + > .search-container + > .search-space + > .search-layout + > .results-container + .result-card + > ul + > li + > p.match-tag { color: var(--tertiary); } @@ -246,6 +303,11 @@ color: var(--gray); } -.search > .search-container > .search-space > .search-layout > .results-container .result-card.no-match { +.search + > .search-container + > .search-space + > .search-layout + > .results-container + .result-card.no-match { cursor: default; } diff --git a/test/__mocks__/scriptMock.ts b/test/__mocks__/scriptMock.ts new file mode 100644 index 0000000..9cf3c27 --- /dev/null +++ b/test/__mocks__/scriptMock.ts @@ -0,0 +1 @@ +export default ""; diff --git a/test/__mocks__/styleMock.ts b/test/__mocks__/styleMock.ts new file mode 100644 index 0000000..9cf3c27 --- /dev/null +++ b/test/__mocks__/styleMock.ts @@ -0,0 +1 @@ +export default ""; diff --git a/test/emitter.test.ts b/test/emitter.test.ts deleted file mode 100644 index 67b31e1..0000000 --- a/test/emitter.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { describe, expect, it } from "vitest"; -import path from "node:path"; -import fs from "node:fs/promises"; -import { tmpdir } from "node:os"; -import { ExampleEmitter } from "../src/emitter"; -import { createCtx, createProcessedContent } from "./helpers"; - -describe("ExampleEmitter", () => { - it("writes a manifest to the output directory", async () => { - const outputDir = await fs.mkdtemp(path.join(tmpdir(), "quartz-plugin-")); - const ctx = createCtx({ argv: { output: outputDir } }); - const emitter = ExampleEmitter({ manifestSlug: "manifest" }); - - const content = [ - createProcessedContent({ - slug: "hello-world", - filePath: "notes/hello-world.md", - frontmatter: { title: "Hello", tags: ["docs"] }, - }), - ]; - - const result = await emitter.emit(ctx, content, { - css: [], - js: [], - additionalHead: [], - }); - const outputPaths = Array.isArray(result) ? result : await collectAsync(result); - const outputPath = outputPaths[0]; - if (!outputPath) { - throw new Error("Expected emitter to return an output path"); - } - const manifest = JSON.parse(await fs.readFile(outputPath, "utf8")); - - expect(outputPath).toContain("manifest.json"); - expect(manifest.pages[0].slug).toBe("hello-world"); - }); -}); - -const collectAsync = async (iterable: AsyncIterable): Promise => { - const results: T[] = []; - for await (const item of iterable) { - results.push(item); - } - return results; -}; diff --git a/test/filter.test.ts b/test/filter.test.ts deleted file mode 100644 index 514069a..0000000 --- a/test/filter.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { ExampleFilter } from "../src/filter"; -import { createCtx, createProcessedContent } from "./helpers"; - -describe("ExampleFilter", () => { - it("filters drafts by default", () => { - const ctx = createCtx(); - const filter = ExampleFilter(); - const content = createProcessedContent({ frontmatter: { draft: true } }); - - expect(filter.shouldPublish(ctx, content)).toBe(false); - }); - - it("allows drafts when configured", () => { - const ctx = createCtx(); - const filter = ExampleFilter({ allowDrafts: true }); - const content = createProcessedContent({ frontmatter: { draft: true } }); - - expect(filter.shouldPublish(ctx, content)).toBe(true); - }); -}); diff --git a/test/helpers.ts b/test/helpers.ts deleted file mode 100644 index 6280a01..0000000 --- a/test/helpers.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { BuildCtx } from "@jackyzha0/quartz/util/ctx"; -import type { QuartzConfig } from "@jackyzha0/quartz/cfg"; -import type { ProcessedContent, QuartzPluginData } from "@jackyzha0/quartz/plugins/vfile"; -import { VFile } from "vfile"; - -type BuildCtxOverrides = Omit, "argv"> & { - argv?: Partial; -}; - -export const createCtx = (overrides: BuildCtxOverrides = {}): BuildCtx => { - const { argv: argvOverrides, ...rest } = overrides; - const argv: BuildCtx["argv"] = { - directory: "content", - verbose: false, - output: "dist", - serve: false, - watch: false, - port: 0, - wsPort: 0, - ...argvOverrides, - }; - - return { - buildId: "test-build", - argv, - cfg: {} as QuartzConfig, - allSlugs: [], - allFiles: [], - incremental: false, - ...rest, - }; -}; - -export const createProcessedContent = (data: Partial = {}): ProcessedContent => { - const vfile = new VFile(""); - vfile.data = data; - return [{ type: "root", children: [] }, vfile]; -}; diff --git a/test/search.test.ts b/test/search.test.ts new file mode 100644 index 0000000..f4ffd7d --- /dev/null +++ b/test/search.test.ts @@ -0,0 +1,24 @@ +import { describe, it, expect } from "vitest"; +import Search from "../src/components/Search"; + +describe("Search Component", () => { + it("exports a component factory function", () => { + expect(typeof Search).toBe("function"); + }); + + it("creates a component with default options", () => { + const SearchComponent = Search(); + expect(typeof SearchComponent).toBe("function"); + }); + + it("creates a component with custom options", () => { + const SearchComponent = Search({ enablePreview: false }); + expect(typeof SearchComponent).toBe("function"); + }); + + it("attaches CSS and script to component", () => { + const SearchComponent = Search(); + expect(SearchComponent.css).toBeDefined(); + expect(SearchComponent.afterDOMLoaded).toBeDefined(); + }); +}); diff --git a/test/transformer.test.ts b/test/transformer.test.ts deleted file mode 100644 index e5db05d..0000000 --- a/test/transformer.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { unified } from "unified"; -import remarkParse from "remark-parse"; -import remarkStringify from "remark-stringify"; -import { ExampleTransformer } from "../src/transformer"; -import { createCtx } from "./helpers"; - -describe("ExampleTransformer", () => { - it("highlights text wrapped in the token", async () => { - const ctx = createCtx(); - const transformer = ExampleTransformer({ highlightToken: "==" }); - const plugins = transformer.markdownPlugins?.(ctx) ?? []; - - const file = await unified() - .use(remarkParse) - .use(plugins) - .use(remarkStringify) - .process("Hello ==Quartz=="); - - expect(String(file)).toContain("**Quartz**"); - }); -}); diff --git a/vitest.config.ts b/vitest.config.ts index d7d0fa2..4cbc341 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,9 +1,18 @@ import { defineConfig } from "vitest/config"; +import path from "path"; export default defineConfig({ test: { environment: "node", include: ["test/**/*.test.ts"], reporters: ["default"], + alias: { + "./styles/search.scss": path.resolve(__dirname, "test/__mocks__/styleMock.ts"), + "./scripts/search.inline.ts": path.resolve(__dirname, "test/__mocks__/scriptMock.ts"), + }, + }, + esbuild: { + jsx: "automatic", + jsxImportSource: "preact", }, });