@specglass/theme-default 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/code-tabs.test.d.ts +2 -0
- package/dist/__tests__/code-tabs.test.d.ts.map +1 -0
- package/dist/__tests__/code-tabs.test.js +219 -0
- package/dist/__tests__/code-tabs.test.js.map +1 -0
- package/dist/__tests__/copy-button.test.d.ts +2 -0
- package/dist/__tests__/copy-button.test.d.ts.map +1 -0
- package/dist/__tests__/copy-button.test.js +116 -0
- package/dist/__tests__/copy-button.test.js.map +1 -0
- package/dist/__tests__/search-palette.test.d.ts +2 -0
- package/dist/__tests__/search-palette.test.d.ts.map +1 -0
- package/dist/__tests__/search-palette.test.js +71 -0
- package/dist/__tests__/search-palette.test.js.map +1 -0
- package/dist/__tests__/shiki.test.d.ts +2 -0
- package/dist/__tests__/shiki.test.d.ts.map +1 -0
- package/dist/__tests__/shiki.test.js +37 -0
- package/dist/__tests__/shiki.test.js.map +1 -0
- package/dist/__tests__/theme-css.test.d.ts +2 -0
- package/dist/__tests__/theme-css.test.d.ts.map +1 -0
- package/dist/__tests__/theme-css.test.js +124 -0
- package/dist/__tests__/theme-css.test.js.map +1 -0
- package/dist/__tests__/theme-helpers.test.d.ts +2 -0
- package/dist/__tests__/theme-helpers.test.d.ts.map +1 -0
- package/dist/__tests__/theme-helpers.test.js +81 -0
- package/dist/__tests__/theme-helpers.test.js.map +1 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/islands/CodeTabs.d.ts +21 -0
- package/dist/islands/CodeTabs.d.ts.map +1 -0
- package/dist/islands/CodeTabs.js +125 -0
- package/dist/islands/CodeTabs.js.map +1 -0
- package/dist/islands/CopyButton.d.ts +16 -0
- package/dist/islands/CopyButton.d.ts.map +1 -0
- package/dist/islands/CopyButton.js +54 -0
- package/dist/islands/CopyButton.js.map +1 -0
- package/dist/islands/SearchPalette.d.ts +2 -0
- package/dist/islands/SearchPalette.d.ts.map +1 -0
- package/dist/islands/SearchPalette.js +109 -0
- package/dist/islands/SearchPalette.js.map +1 -0
- package/dist/islands/SearchResults.d.ts +2 -0
- package/dist/islands/SearchResults.d.ts.map +1 -0
- package/dist/islands/SearchResults.js +130 -0
- package/dist/islands/SearchResults.js.map +1 -0
- package/dist/islands/ThemeToggle.d.ts +12 -0
- package/dist/islands/ThemeToggle.d.ts.map +1 -0
- package/dist/islands/ThemeToggle.js +43 -0
- package/dist/islands/ThemeToggle.js.map +1 -0
- package/dist/layouts/DocPage.test.d.ts +2 -0
- package/dist/layouts/DocPage.test.d.ts.map +1 -0
- package/dist/layouts/DocPage.test.js +165 -0
- package/dist/layouts/DocPage.test.js.map +1 -0
- package/dist/lib/utils.d.ts +10 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +13 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/scripts/code-block-enhancer.d.ts +16 -0
- package/dist/scripts/code-block-enhancer.d.ts.map +1 -0
- package/dist/scripts/code-block-enhancer.js +55 -0
- package/dist/scripts/code-block-enhancer.js.map +1 -0
- package/dist/ui/command.d.ts +87 -0
- package/dist/ui/command.d.ts.map +1 -0
- package/dist/ui/command.js +28 -0
- package/dist/ui/command.js.map +1 -0
- package/dist/ui/dialog.d.ts +20 -0
- package/dist/ui/dialog.d.ts.map +1 -0
- package/dist/ui/dialog.js +22 -0
- package/dist/ui/dialog.js.map +1 -0
- package/dist/utils/parse-highlight-range.d.ts +12 -0
- package/dist/utils/parse-highlight-range.d.ts.map +1 -0
- package/dist/utils/parse-highlight-range.js +40 -0
- package/dist/utils/parse-highlight-range.js.map +1 -0
- package/dist/utils/parse-highlight-range.test.d.ts +2 -0
- package/dist/utils/parse-highlight-range.test.d.ts.map +1 -0
- package/dist/utils/parse-highlight-range.test.js +32 -0
- package/dist/utils/parse-highlight-range.test.js.map +1 -0
- package/dist/utils/schema-renderer.d.ts +38 -0
- package/dist/utils/schema-renderer.d.ts.map +1 -0
- package/dist/utils/schema-renderer.js +115 -0
- package/dist/utils/schema-renderer.js.map +1 -0
- package/dist/utils/schema-renderer.test.d.ts +2 -0
- package/dist/utils/schema-renderer.test.d.ts.map +1 -0
- package/dist/utils/schema-renderer.test.js +219 -0
- package/dist/utils/schema-renderer.test.js.map +1 -0
- package/dist/utils/shiki.d.ts +20 -0
- package/dist/utils/shiki.d.ts.map +1 -0
- package/dist/utils/shiki.js +84 -0
- package/dist/utils/shiki.js.map +1 -0
- package/dist/utils/sidebar-helpers.d.ts +10 -0
- package/dist/utils/sidebar-helpers.d.ts.map +1 -0
- package/dist/utils/sidebar-helpers.js +14 -0
- package/dist/utils/sidebar-helpers.js.map +1 -0
- package/dist/utils/theme-css.d.ts +21 -0
- package/dist/utils/theme-css.d.ts.map +1 -0
- package/dist/utils/theme-css.js +77 -0
- package/dist/utils/theme-css.js.map +1 -0
- package/dist/utils/theme-helpers.d.ts +28 -0
- package/dist/utils/theme-helpers.d.ts.map +1 -0
- package/dist/utils/theme-helpers.js +55 -0
- package/dist/utils/theme-helpers.js.map +1 -0
- package/dist/utils/toc-helpers.d.ts +12 -0
- package/dist/utils/toc-helpers.d.ts.map +1 -0
- package/dist/utils/toc-helpers.js +9 -0
- package/dist/utils/toc-helpers.js.map +1 -0
- package/package.json +68 -0
- package/src/components/ApiAuth.astro +116 -0
- package/src/components/ApiEndpoint.astro +75 -0
- package/src/components/ApiNavigation.astro +110 -0
- package/src/components/ApiParameters.astro +204 -0
- package/src/components/ApiResponse.astro +144 -0
- package/src/components/Callout.astro +54 -0
- package/src/components/Card.astro +46 -0
- package/src/components/CodeBlock.astro +142 -0
- package/src/components/CodeBlockGroup.astro +196 -0
- package/src/components/CodeTabs.astro +53 -0
- package/src/components/Footer.astro +41 -0
- package/src/components/Header.astro +80 -0
- package/src/components/Sidebar.astro +117 -0
- package/src/components/TabItem.astro +24 -0
- package/src/components/TableOfContents.astro +111 -0
- package/src/components/Tabs.astro +185 -0
- package/src/islands/CodeTabs.tsx +212 -0
- package/src/islands/CopyButton.tsx +101 -0
- package/src/islands/SearchPalette.tsx +307 -0
- package/src/islands/SearchResults.tsx +301 -0
- package/src/islands/ThemeToggle.tsx +107 -0
- package/src/layouts/ApiReferencePage.astro +239 -0
- package/src/layouts/DocPage.astro +199 -0
- package/src/layouts/DocPage.test.ts +183 -0
- package/src/layouts/LandingPage.astro +143 -0
- package/src/lib/utils.ts +13 -0
- package/src/styles/global.css +241 -0
- package/src/utils/parse-highlight-range.test.ts +40 -0
- package/src/utils/parse-highlight-range.ts +41 -0
- package/src/utils/schema-renderer.test.ts +269 -0
- package/src/utils/schema-renderer.ts +152 -0
- package/src/utils/shiki.ts +99 -0
- package/src/utils/sidebar-helpers.ts +24 -0
- package/src/utils/theme-css.ts +101 -0
- package/src/utils/theme-helpers.ts +59 -0
- package/src/utils/toc-helpers.ts +11 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"code-tabs.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/code-tabs.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
/**
|
|
3
|
+
* CodeTabs island tests.
|
|
4
|
+
*
|
|
5
|
+
* Tests the CodeTabs React island's hook logic for tab switching,
|
|
6
|
+
* localStorage persistence, and cross-group sync via CustomEvent.
|
|
7
|
+
*
|
|
8
|
+
* Following the established pattern (see copy-button.test.ts): we
|
|
9
|
+
* re-implement the hook logic in the test file to avoid JSX rendering
|
|
10
|
+
* issues with Astro React islands in vitest.
|
|
11
|
+
*/
|
|
12
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
13
|
+
import { renderHook, act } from "@testing-library/react";
|
|
14
|
+
import { useState, useEffect, useCallback, useRef } from "react";
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Re-implement the useCodeTabs hook logic for isolated testing
|
|
17
|
+
// SYNC: Must match the hook in CodeTabs.tsx — update both when changing
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
const LANGUAGE_CHANGE_EVENT = "ndocs-language-change";
|
|
20
|
+
function getStorageKey(syncKey) {
|
|
21
|
+
return `ndocs-preferred-${syncKey}`;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Mirror of the hook logic in CodeTabs.tsx.
|
|
25
|
+
* Must stay in sync with the real implementation.
|
|
26
|
+
*/
|
|
27
|
+
function useCodeTabs({ languages, syncKey = "sdk-language" }) {
|
|
28
|
+
const storageKey = getStorageKey(syncKey);
|
|
29
|
+
// Always start at index 0 — SSR-safe. Stored preference restored in useEffect.
|
|
30
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
31
|
+
const isInternalChange = useRef(false);
|
|
32
|
+
// Restore stored preference after hydration
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
try {
|
|
35
|
+
const stored = localStorage.getItem(storageKey);
|
|
36
|
+
if (stored) {
|
|
37
|
+
const idx = languages.indexOf(stored);
|
|
38
|
+
if (idx !== -1 && idx !== 0) {
|
|
39
|
+
setActiveIndex(idx);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// localStorage unavailable
|
|
45
|
+
}
|
|
46
|
+
}, [storageKey, languages]);
|
|
47
|
+
const setTab = useCallback((index) => {
|
|
48
|
+
if (index < 0 || index >= languages.length)
|
|
49
|
+
return;
|
|
50
|
+
setActiveIndex(index);
|
|
51
|
+
const language = languages[index];
|
|
52
|
+
if (language) {
|
|
53
|
+
try {
|
|
54
|
+
localStorage.setItem(storageKey, language);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// localStorage unavailable
|
|
58
|
+
}
|
|
59
|
+
isInternalChange.current = true;
|
|
60
|
+
window.dispatchEvent(new CustomEvent(LANGUAGE_CHANGE_EVENT, {
|
|
61
|
+
detail: { syncKey, language },
|
|
62
|
+
}));
|
|
63
|
+
isInternalChange.current = false;
|
|
64
|
+
}
|
|
65
|
+
}, [languages, storageKey, syncKey]);
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
const handler = (e) => {
|
|
68
|
+
if (isInternalChange.current)
|
|
69
|
+
return;
|
|
70
|
+
const detail = e.detail;
|
|
71
|
+
if (detail?.syncKey === syncKey) {
|
|
72
|
+
const idx = languages.indexOf(detail.language);
|
|
73
|
+
if (idx !== -1) {
|
|
74
|
+
setActiveIndex(idx);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
window.addEventListener(LANGUAGE_CHANGE_EVENT, handler);
|
|
79
|
+
return () => window.removeEventListener(LANGUAGE_CHANGE_EVENT, handler);
|
|
80
|
+
}, [languages, syncKey]);
|
|
81
|
+
return { activeIndex, setTab };
|
|
82
|
+
}
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Tests
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
describe("CodeTabs", () => {
|
|
87
|
+
beforeEach(() => {
|
|
88
|
+
localStorage.clear();
|
|
89
|
+
// Remove any lingering event listeners by replacing window dispatch
|
|
90
|
+
vi.restoreAllMocks();
|
|
91
|
+
});
|
|
92
|
+
afterEach(() => {
|
|
93
|
+
localStorage.clear();
|
|
94
|
+
vi.restoreAllMocks();
|
|
95
|
+
});
|
|
96
|
+
describe("Module exports", () => {
|
|
97
|
+
it("exports CodeTabs as a named export", async () => {
|
|
98
|
+
const mod = await import("../islands/CodeTabs");
|
|
99
|
+
expect(mod.CodeTabs).toBeDefined();
|
|
100
|
+
expect(typeof mod.CodeTabs).toBe("function");
|
|
101
|
+
});
|
|
102
|
+
it("CodeTabs is a function component", async () => {
|
|
103
|
+
const { CodeTabs } = await import("../islands/CodeTabs");
|
|
104
|
+
expect(CodeTabs.name).toBe("CodeTabs");
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
describe("useCodeTabs hook — tab switching", () => {
|
|
108
|
+
it("starts at index 0 when no stored preference", () => {
|
|
109
|
+
const { result } = renderHook(() => useCodeTabs({ languages: ["python", "javascript", "curl"] }));
|
|
110
|
+
expect(result.current.activeIndex).toBe(0);
|
|
111
|
+
});
|
|
112
|
+
it("switches to the selected tab", () => {
|
|
113
|
+
const { result } = renderHook(() => useCodeTabs({ languages: ["python", "javascript", "curl"] }));
|
|
114
|
+
act(() => {
|
|
115
|
+
result.current.setTab(2);
|
|
116
|
+
});
|
|
117
|
+
expect(result.current.activeIndex).toBe(2);
|
|
118
|
+
});
|
|
119
|
+
it("ignores out-of-bounds setTab (negative)", () => {
|
|
120
|
+
const { result } = renderHook(() => useCodeTabs({ languages: ["python", "javascript", "curl"] }));
|
|
121
|
+
act(() => {
|
|
122
|
+
result.current.setTab(-1);
|
|
123
|
+
});
|
|
124
|
+
expect(result.current.activeIndex).toBe(0);
|
|
125
|
+
});
|
|
126
|
+
it("ignores out-of-bounds setTab (too high)", () => {
|
|
127
|
+
const { result } = renderHook(() => useCodeTabs({ languages: ["python", "javascript", "curl"] }));
|
|
128
|
+
act(() => {
|
|
129
|
+
result.current.setTab(99);
|
|
130
|
+
});
|
|
131
|
+
expect(result.current.activeIndex).toBe(0);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
describe("useCodeTabs hook — localStorage persistence", () => {
|
|
135
|
+
it("saves language to localStorage on tab change", () => {
|
|
136
|
+
const { result } = renderHook(() => useCodeTabs({ languages: ["python", "javascript", "curl"] }));
|
|
137
|
+
act(() => {
|
|
138
|
+
result.current.setTab(1);
|
|
139
|
+
});
|
|
140
|
+
expect(localStorage.getItem("ndocs-preferred-sdk-language")).toBe("javascript");
|
|
141
|
+
});
|
|
142
|
+
it("reads stored preference on mount", () => {
|
|
143
|
+
localStorage.setItem("ndocs-preferred-sdk-language", "curl");
|
|
144
|
+
const { result } = renderHook(() => useCodeTabs({ languages: ["python", "javascript", "curl"] }));
|
|
145
|
+
expect(result.current.activeIndex).toBe(2);
|
|
146
|
+
});
|
|
147
|
+
it("falls back to index 0 when stored language is not in tabs", () => {
|
|
148
|
+
localStorage.setItem("ndocs-preferred-sdk-language", "ruby");
|
|
149
|
+
const { result } = renderHook(() => useCodeTabs({ languages: ["python", "javascript", "curl"] }));
|
|
150
|
+
expect(result.current.activeIndex).toBe(0);
|
|
151
|
+
});
|
|
152
|
+
it("supports custom syncKey for separate persistence", () => {
|
|
153
|
+
const { result } = renderHook(() => useCodeTabs({
|
|
154
|
+
languages: ["rest", "graphql"],
|
|
155
|
+
syncKey: "api-style",
|
|
156
|
+
}));
|
|
157
|
+
act(() => {
|
|
158
|
+
result.current.setTab(1);
|
|
159
|
+
});
|
|
160
|
+
expect(localStorage.getItem("ndocs-preferred-api-style")).toBe("graphql");
|
|
161
|
+
// Default key should remain unset
|
|
162
|
+
expect(localStorage.getItem("ndocs-preferred-sdk-language")).toBeNull();
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
describe("useCodeTabs hook — cross-group sync", () => {
|
|
166
|
+
it("broadcasts language change via CustomEvent", () => {
|
|
167
|
+
const handler = vi.fn();
|
|
168
|
+
window.addEventListener(LANGUAGE_CHANGE_EVENT, handler);
|
|
169
|
+
const { result } = renderHook(() => useCodeTabs({ languages: ["python", "javascript"] }));
|
|
170
|
+
act(() => {
|
|
171
|
+
result.current.setTab(1);
|
|
172
|
+
});
|
|
173
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
174
|
+
const event = handler.mock.calls[0][0];
|
|
175
|
+
expect(event.detail).toEqual({
|
|
176
|
+
syncKey: "sdk-language",
|
|
177
|
+
language: "javascript",
|
|
178
|
+
});
|
|
179
|
+
window.removeEventListener(LANGUAGE_CHANGE_EVENT, handler);
|
|
180
|
+
});
|
|
181
|
+
it("listens for external language change events", () => {
|
|
182
|
+
const { result } = renderHook(() => useCodeTabs({ languages: ["python", "javascript", "curl"] }));
|
|
183
|
+
expect(result.current.activeIndex).toBe(0);
|
|
184
|
+
// Simulate an external event (from another CodeTabs instance)
|
|
185
|
+
act(() => {
|
|
186
|
+
window.dispatchEvent(new CustomEvent(LANGUAGE_CHANGE_EVENT, {
|
|
187
|
+
detail: { syncKey: "sdk-language", language: "curl" },
|
|
188
|
+
}));
|
|
189
|
+
});
|
|
190
|
+
expect(result.current.activeIndex).toBe(2);
|
|
191
|
+
});
|
|
192
|
+
it("ignores events with a different syncKey", () => {
|
|
193
|
+
const { result } = renderHook(() => useCodeTabs({ languages: ["python", "javascript"] }));
|
|
194
|
+
act(() => {
|
|
195
|
+
window.dispatchEvent(new CustomEvent(LANGUAGE_CHANGE_EVENT, {
|
|
196
|
+
detail: { syncKey: "other-key", language: "javascript" },
|
|
197
|
+
}));
|
|
198
|
+
});
|
|
199
|
+
// Should NOT have changed
|
|
200
|
+
expect(result.current.activeIndex).toBe(0);
|
|
201
|
+
});
|
|
202
|
+
it("ignores events for languages not in its tab list", () => {
|
|
203
|
+
const { result } = renderHook(() => useCodeTabs({ languages: ["python", "javascript"] }));
|
|
204
|
+
act(() => {
|
|
205
|
+
window.dispatchEvent(new CustomEvent(LANGUAGE_CHANGE_EVENT, {
|
|
206
|
+
detail: { syncKey: "sdk-language", language: "ruby" },
|
|
207
|
+
}));
|
|
208
|
+
});
|
|
209
|
+
expect(result.current.activeIndex).toBe(0);
|
|
210
|
+
});
|
|
211
|
+
it("cleans up event listener on unmount", () => {
|
|
212
|
+
const spy = vi.spyOn(window, "removeEventListener");
|
|
213
|
+
const { unmount } = renderHook(() => useCodeTabs({ languages: ["python", "javascript"] }));
|
|
214
|
+
unmount();
|
|
215
|
+
expect(spy).toHaveBeenCalledWith(LANGUAGE_CHANGE_EVENT, expect.any(Function));
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
//# sourceMappingURL=code-tabs.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"code-tabs.test.js","sourceRoot":"","sources":["../../src/__tests__/code-tabs.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copy-button.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/copy-button.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
3
|
+
import { renderHook, act } from "@testing-library/react";
|
|
4
|
+
import { useState, useCallback, useRef } from "react";
|
|
5
|
+
/**
|
|
6
|
+
* Tests for CopyButton island behavior.
|
|
7
|
+
*
|
|
8
|
+
* Since CopyButton uses navigator.clipboard (browser API), we mock it.
|
|
9
|
+
* We test the core logic: copied state, timeout, aria-label.
|
|
10
|
+
*
|
|
11
|
+
* NOTE: The hook logic is re-implemented here rather than importing the
|
|
12
|
+
* CopyButton component directly. This is intentional — Astro React
|
|
13
|
+
* islands with JSX don't render cleanly in vitest without a full Astro
|
|
14
|
+
* build context. This matches the ThemeToggle test pattern. If the hook
|
|
15
|
+
* logic changes in CopyButton.tsx, these tests must be updated to match.
|
|
16
|
+
*/
|
|
17
|
+
// Re-implement the hook logic for isolated testing (avoids JSX rendering issues
|
|
18
|
+
// with the React island in vitest — consistent with the ThemeToggle test pattern)
|
|
19
|
+
function useCopyLogic(code) {
|
|
20
|
+
const [copied, setCopied] = useState(false);
|
|
21
|
+
const timeoutRef = useRef(null);
|
|
22
|
+
const handleCopy = useCallback(() => {
|
|
23
|
+
if (timeoutRef.current) {
|
|
24
|
+
clearTimeout(timeoutRef.current);
|
|
25
|
+
}
|
|
26
|
+
navigator.clipboard.writeText(code).then(() => {
|
|
27
|
+
setCopied(true);
|
|
28
|
+
timeoutRef.current = setTimeout(() => {
|
|
29
|
+
setCopied(false);
|
|
30
|
+
timeoutRef.current = null;
|
|
31
|
+
}, 2000);
|
|
32
|
+
});
|
|
33
|
+
}, [code]);
|
|
34
|
+
return { copied, handleCopy };
|
|
35
|
+
}
|
|
36
|
+
describe("CopyButton logic", () => {
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
vi.useFakeTimers();
|
|
39
|
+
// Mock clipboard API
|
|
40
|
+
Object.assign(navigator, {
|
|
41
|
+
clipboard: {
|
|
42
|
+
writeText: vi.fn().mockResolvedValue(undefined),
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
afterEach(() => {
|
|
47
|
+
vi.useRealTimers();
|
|
48
|
+
vi.restoreAllMocks();
|
|
49
|
+
});
|
|
50
|
+
it("starts with copied = false", () => {
|
|
51
|
+
const { result } = renderHook(() => useCopyLogic("hello"));
|
|
52
|
+
expect(result.current.copied).toBe(false);
|
|
53
|
+
});
|
|
54
|
+
it("sets copied = true after handleCopy", async () => {
|
|
55
|
+
const { result } = renderHook(() => useCopyLogic("hello"));
|
|
56
|
+
await act(async () => {
|
|
57
|
+
result.current.handleCopy();
|
|
58
|
+
// Flush the promise
|
|
59
|
+
await Promise.resolve();
|
|
60
|
+
});
|
|
61
|
+
expect(result.current.copied).toBe(true);
|
|
62
|
+
expect(navigator.clipboard.writeText).toHaveBeenCalledWith("hello");
|
|
63
|
+
});
|
|
64
|
+
it("resets copied = false after 2 seconds", async () => {
|
|
65
|
+
const { result } = renderHook(() => useCopyLogic("hello"));
|
|
66
|
+
await act(async () => {
|
|
67
|
+
result.current.handleCopy();
|
|
68
|
+
await Promise.resolve();
|
|
69
|
+
});
|
|
70
|
+
expect(result.current.copied).toBe(true);
|
|
71
|
+
act(() => {
|
|
72
|
+
vi.advanceTimersByTime(2000);
|
|
73
|
+
});
|
|
74
|
+
expect(result.current.copied).toBe(false);
|
|
75
|
+
});
|
|
76
|
+
it("copies the exact code string provided", async () => {
|
|
77
|
+
const code = 'const x = "multiline\\ncode";';
|
|
78
|
+
const { result } = renderHook(() => useCopyLogic(code));
|
|
79
|
+
await act(async () => {
|
|
80
|
+
result.current.handleCopy();
|
|
81
|
+
await Promise.resolve();
|
|
82
|
+
});
|
|
83
|
+
expect(navigator.clipboard.writeText).toHaveBeenCalledWith(code);
|
|
84
|
+
});
|
|
85
|
+
it("resets timeout on rapid successive clicks", async () => {
|
|
86
|
+
const { result } = renderHook(() => useCopyLogic("hello"));
|
|
87
|
+
// First click
|
|
88
|
+
await act(async () => {
|
|
89
|
+
result.current.handleCopy();
|
|
90
|
+
await Promise.resolve();
|
|
91
|
+
});
|
|
92
|
+
expect(result.current.copied).toBe(true);
|
|
93
|
+
// Advance 1 second (not yet 2s)
|
|
94
|
+
act(() => {
|
|
95
|
+
vi.advanceTimersByTime(1000);
|
|
96
|
+
});
|
|
97
|
+
// Second click — should reset timer
|
|
98
|
+
await act(async () => {
|
|
99
|
+
result.current.handleCopy();
|
|
100
|
+
await Promise.resolve();
|
|
101
|
+
});
|
|
102
|
+
expect(result.current.copied).toBe(true);
|
|
103
|
+
// Advance 1.5 seconds — the first timeout would have expired by now,
|
|
104
|
+
// but the second click reset it
|
|
105
|
+
act(() => {
|
|
106
|
+
vi.advanceTimersByTime(1500);
|
|
107
|
+
});
|
|
108
|
+
expect(result.current.copied).toBe(true);
|
|
109
|
+
// Full 2s from second click
|
|
110
|
+
act(() => {
|
|
111
|
+
vi.advanceTimersByTime(500);
|
|
112
|
+
});
|
|
113
|
+
expect(result.current.copied).toBe(false);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
//# sourceMappingURL=copy-button.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copy-button.test.js","sourceRoot":"","sources":["../../src/__tests__/copy-button.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-palette.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/search-palette.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SearchPalette component tests.
|
|
3
|
+
*
|
|
4
|
+
* Tests the SearchPalette island's rendering, trigger button,
|
|
5
|
+
* and keyboard shortcut behavior. Pagefind is mocked since it
|
|
6
|
+
* requires a build-time generated index.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, it, expect, vi } from "vitest";
|
|
9
|
+
// Mock Pagefind — it's loaded dynamically and won't exist in test env
|
|
10
|
+
vi.mock("/pagefind/pagefind.js", () => ({
|
|
11
|
+
init: vi.fn(),
|
|
12
|
+
search: vi.fn().mockResolvedValue({ results: [] }),
|
|
13
|
+
}));
|
|
14
|
+
describe("SearchPalette", () => {
|
|
15
|
+
describe("Module exports", () => {
|
|
16
|
+
it("exports SearchPalette as a named export", async () => {
|
|
17
|
+
const mod = await import("../islands/SearchPalette");
|
|
18
|
+
expect(mod.SearchPalette).toBeDefined();
|
|
19
|
+
expect(typeof mod.SearchPalette).toBe("function");
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
describe("Component structure", () => {
|
|
23
|
+
it("SearchPalette is a function component", async () => {
|
|
24
|
+
const { SearchPalette } = await import("../islands/SearchPalette");
|
|
25
|
+
// React function components have a name
|
|
26
|
+
expect(SearchPalette.name).toBe("SearchPalette");
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
describe("Constants", () => {
|
|
30
|
+
it("uses expected debounce and result limits", async () => {
|
|
31
|
+
// Verify the source contains expected constants by importing the module
|
|
32
|
+
const mod = await import("../islands/SearchPalette");
|
|
33
|
+
// The component exists and is callable — constants are internal
|
|
34
|
+
expect(mod.SearchPalette).toBeDefined();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
describe("ui/command", () => {
|
|
39
|
+
it("exports all expected shadcn Command components", async () => {
|
|
40
|
+
const mod = await import("../ui/command");
|
|
41
|
+
expect(mod.Command).toBeDefined();
|
|
42
|
+
expect(mod.CommandDialog).toBeDefined();
|
|
43
|
+
expect(mod.CommandInput).toBeDefined();
|
|
44
|
+
expect(mod.CommandList).toBeDefined();
|
|
45
|
+
expect(mod.CommandEmpty).toBeDefined();
|
|
46
|
+
expect(mod.CommandGroup).toBeDefined();
|
|
47
|
+
expect(mod.CommandItem).toBeDefined();
|
|
48
|
+
expect(mod.CommandShortcut).toBeDefined();
|
|
49
|
+
expect(mod.CommandSeparator).toBeDefined();
|
|
50
|
+
});
|
|
51
|
+
it("CommandDialog is a function (not forwardRef wrapper)", async () => {
|
|
52
|
+
const { CommandDialog } = await import("../ui/command");
|
|
53
|
+
expect(typeof CommandDialog).toBe("function");
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
describe("ui/dialog", () => {
|
|
57
|
+
it("exports all expected shadcn Dialog components", async () => {
|
|
58
|
+
const mod = await import("../ui/dialog");
|
|
59
|
+
expect(mod.Dialog).toBeDefined();
|
|
60
|
+
expect(mod.DialogPortal).toBeDefined();
|
|
61
|
+
expect(mod.DialogOverlay).toBeDefined();
|
|
62
|
+
expect(mod.DialogClose).toBeDefined();
|
|
63
|
+
expect(mod.DialogTrigger).toBeDefined();
|
|
64
|
+
expect(mod.DialogContent).toBeDefined();
|
|
65
|
+
expect(mod.DialogHeader).toBeDefined();
|
|
66
|
+
expect(mod.DialogFooter).toBeDefined();
|
|
67
|
+
expect(mod.DialogTitle).toBeDefined();
|
|
68
|
+
expect(mod.DialogDescription).toBeDefined();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
//# sourceMappingURL=search-palette.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-palette.test.js","sourceRoot":"","sources":["../../src/__tests__/search-palette.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shiki.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/shiki.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { highlight } from "../utils/shiki.js";
|
|
3
|
+
describe("highlight utility", () => {
|
|
4
|
+
it("returns HTML string with CSS variables for dual themes", async () => {
|
|
5
|
+
const html = await highlight("const x = 1;", "typescript");
|
|
6
|
+
// Output should contain Shiki dual-theme CSS variables
|
|
7
|
+
expect(html).toContain("--shiki-light");
|
|
8
|
+
expect(html).toContain("--shiki-dark");
|
|
9
|
+
// Should be wrapped in <pre><code>
|
|
10
|
+
expect(html).toContain("<pre");
|
|
11
|
+
expect(html).toContain("<code");
|
|
12
|
+
});
|
|
13
|
+
it("falls back to plaintext for unknown languages", async () => {
|
|
14
|
+
// Should not throw — falls back to "text"
|
|
15
|
+
const html = await highlight("hello world", "nonexistent-language");
|
|
16
|
+
expect(html).toContain("hello world");
|
|
17
|
+
expect(html).toContain("<pre");
|
|
18
|
+
});
|
|
19
|
+
it("applies line highlighting decorations", async () => {
|
|
20
|
+
const code = "line1\nline2\nline3";
|
|
21
|
+
const html = await highlight(code, "text", { highlightLines: [2] });
|
|
22
|
+
// Line 2 should have the "highlighted" class
|
|
23
|
+
expect(html).toContain("highlighted");
|
|
24
|
+
});
|
|
25
|
+
it("handles empty code string", async () => {
|
|
26
|
+
const html = await highlight("", "text");
|
|
27
|
+
expect(html).toContain("<pre");
|
|
28
|
+
});
|
|
29
|
+
it("reuses cached highlighter across calls", async () => {
|
|
30
|
+
// Two calls should work without error — verifying cache
|
|
31
|
+
const html1 = await highlight("const a = 1;", "typescript");
|
|
32
|
+
const html2 = await highlight("const b = 2;", "typescript");
|
|
33
|
+
expect(html1).toContain("--shiki-light");
|
|
34
|
+
expect(html2).toContain("--shiki-light");
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
//# sourceMappingURL=shiki.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shiki.test.js","sourceRoot":"","sources":["../../src/__tests__/shiki.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAkB,MAAM,QAAQ,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAE9C,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,EAAE,CAAC,wDAAwD,EAAE,KAAK,IAAI,EAAE;QACtE,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,cAAc,EAAE,YAAY,CAAC,CAAC;QAE3D,uDAAuD;QACvD,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC;QACxC,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;QACvC,mCAAmC;QACnC,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QAC/B,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,KAAK,IAAI,EAAE;QAC7D,0CAA0C;QAC1C,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,aAAa,EAAE,sBAAsB,CAAC,CAAC;QACpE,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;QACtC,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;QACrD,MAAM,IAAI,GAAG,qBAAqB,CAAC;QACnC,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAEpE,6CAA6C;QAC7C,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2BAA2B,EAAE,KAAK,IAAI,EAAE;QACzC,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;QACzC,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;QACtD,wDAAwD;QACxD,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,cAAc,EAAE,YAAY,CAAC,CAAC;QAC5D,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,cAAc,EAAE,YAAY,CAAC,CAAC;QAC5D,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC;QACzC,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"theme-css.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/theme-css.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { generateThemeCSS } from "../utils/theme-css.js";
|
|
3
|
+
describe("generateThemeCSS", () => {
|
|
4
|
+
it("returns empty string when no theme config is set", () => {
|
|
5
|
+
const config = {};
|
|
6
|
+
expect(generateThemeCSS(config)).toBe("");
|
|
7
|
+
});
|
|
8
|
+
it("returns empty string when theme exists but no values are set", () => {
|
|
9
|
+
const config = { theme: {} };
|
|
10
|
+
expect(generateThemeCSS(config)).toBe("");
|
|
11
|
+
});
|
|
12
|
+
it("generates --color-primary from primaryColor", () => {
|
|
13
|
+
const config = { theme: { primaryColor: "#3b82f6" } };
|
|
14
|
+
const css = generateThemeCSS(config);
|
|
15
|
+
expect(css).toContain("--color-primary: #3b82f6;");
|
|
16
|
+
expect(css).toMatch(/^:root \{/);
|
|
17
|
+
});
|
|
18
|
+
it("generates font overrides from fontFamily and codeFontFamily", () => {
|
|
19
|
+
const config = {
|
|
20
|
+
theme: { fontFamily: "'Poppins', sans-serif", codeFontFamily: "'Fira Code', monospace" },
|
|
21
|
+
};
|
|
22
|
+
const css = generateThemeCSS(config);
|
|
23
|
+
expect(css).toContain("--font-sans: 'Poppins', sans-serif;");
|
|
24
|
+
expect(css).toContain("--font-mono: 'Fira Code', monospace;");
|
|
25
|
+
});
|
|
26
|
+
it("maps string color values to -dark variants (backward compat)", () => {
|
|
27
|
+
const config = {
|
|
28
|
+
theme: {
|
|
29
|
+
colors: {
|
|
30
|
+
primary: "oklch(0.7 0.15 250)",
|
|
31
|
+
background: "#0a0a0a",
|
|
32
|
+
foreground: "#fafafa",
|
|
33
|
+
muted: "#a1a1aa",
|
|
34
|
+
border: "#27272a",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
const css = generateThemeCSS(config);
|
|
39
|
+
expect(css).toContain("--color-primary: oklch(0.7 0.15 250);");
|
|
40
|
+
// String values → dark-only overrides
|
|
41
|
+
expect(css).toContain("--color-surface-dark: #0a0a0a;");
|
|
42
|
+
expect(css).toContain("--color-text-dark: #fafafa;");
|
|
43
|
+
expect(css).toContain("--color-text-muted-dark: #a1a1aa;");
|
|
44
|
+
expect(css).toContain("--color-border-dark: #27272a;");
|
|
45
|
+
// Should NOT set base (light) tokens
|
|
46
|
+
expect(css).not.toMatch(/--color-surface: /);
|
|
47
|
+
expect(css).not.toMatch(/--color-text: /);
|
|
48
|
+
});
|
|
49
|
+
it("maps { light, dark } pairs to both base and -dark variants", () => {
|
|
50
|
+
const config = {
|
|
51
|
+
theme: {
|
|
52
|
+
colors: {
|
|
53
|
+
background: { light: "#ffffff", dark: "#0a0a0a" },
|
|
54
|
+
foreground: { light: "#1f2937", dark: "#fafafa" },
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
const css = generateThemeCSS(config);
|
|
59
|
+
// Light values → base tokens
|
|
60
|
+
expect(css).toContain("--color-surface: #ffffff;");
|
|
61
|
+
expect(css).toContain("--color-text: #1f2937;");
|
|
62
|
+
// Dark values → -dark variants
|
|
63
|
+
expect(css).toContain("--color-surface-dark: #0a0a0a;");
|
|
64
|
+
expect(css).toContain("--color-text-dark: #fafafa;");
|
|
65
|
+
});
|
|
66
|
+
it("handles partial pairs (dark-only object)", () => {
|
|
67
|
+
const config = {
|
|
68
|
+
theme: {
|
|
69
|
+
colors: {
|
|
70
|
+
background: { dark: "#0a0a0a" },
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
const css = generateThemeCSS(config);
|
|
75
|
+
expect(css).toContain("--color-surface-dark: #0a0a0a;");
|
|
76
|
+
expect(css).not.toMatch(/--color-surface: /);
|
|
77
|
+
});
|
|
78
|
+
it("handles partial pairs (light-only object)", () => {
|
|
79
|
+
const config = {
|
|
80
|
+
theme: {
|
|
81
|
+
colors: {
|
|
82
|
+
background: { light: "#ffffff" },
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
const css = generateThemeCSS(config);
|
|
87
|
+
expect(css).toContain("--color-surface: #ffffff;");
|
|
88
|
+
expect(css).not.toContain("--color-surface-dark");
|
|
89
|
+
});
|
|
90
|
+
it("prefers primaryColor over colors.primary (shorthand wins)", () => {
|
|
91
|
+
const config = {
|
|
92
|
+
theme: {
|
|
93
|
+
primaryColor: "#direct",
|
|
94
|
+
colors: { primary: "#nested" },
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
const css = generateThemeCSS(config);
|
|
98
|
+
expect(css).toContain("--color-primary: #direct;");
|
|
99
|
+
expect(css).not.toContain("#nested");
|
|
100
|
+
});
|
|
101
|
+
it("falls back to colors.primary when primaryColor is not set", () => {
|
|
102
|
+
const config = {
|
|
103
|
+
theme: { colors: { primary: "#nested" } },
|
|
104
|
+
};
|
|
105
|
+
const css = generateThemeCSS(config);
|
|
106
|
+
expect(css).toContain("--color-primary: #nested;");
|
|
107
|
+
});
|
|
108
|
+
it("maps accentColor to --color-primary-light", () => {
|
|
109
|
+
const config = { theme: { accentColor: "#8b5cf6" } };
|
|
110
|
+
const css = generateThemeCSS(config);
|
|
111
|
+
expect(css).toContain("--color-primary-light: #8b5cf6;");
|
|
112
|
+
});
|
|
113
|
+
it("only includes properties that have values", () => {
|
|
114
|
+
const config = {
|
|
115
|
+
theme: { primaryColor: "#3b82f6" },
|
|
116
|
+
};
|
|
117
|
+
const css = generateThemeCSS(config);
|
|
118
|
+
// Should NOT include font or other missing properties
|
|
119
|
+
expect(css).not.toContain("--font-sans");
|
|
120
|
+
expect(css).not.toContain("--color-surface-dark");
|
|
121
|
+
expect(css).not.toContain("--color-surface:");
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
//# sourceMappingURL=theme-css.test.js.map
|