@tomehq/theme 0.1.0
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/LICENSE +21 -0
- package/dist/chunk-BZGWSKT2.js +573 -0
- package/dist/chunk-FWBTK5TL.js +1444 -0
- package/dist/chunk-JZRT4WNC.js +1441 -0
- package/dist/chunk-LIMYFTPC.js +1468 -0
- package/dist/chunk-MEP7P6A7.js +1500 -0
- package/dist/chunk-QCWZYABW.js +1468 -0
- package/dist/chunk-RKTT3ZEX.js +1500 -0
- package/dist/chunk-UKYFJSUA.js +509 -0
- package/dist/entry.d.ts +5 -0
- package/dist/entry.js +6 -0
- package/dist/index.d.ts +200 -0
- package/dist/index.js +12 -0
- package/package.json +52 -0
- package/src/AiChat.test.tsx +308 -0
- package/src/AiChat.tsx +439 -0
- package/src/Shell.test.tsx +565 -0
- package/src/Shell.tsx +827 -0
- package/src/entry.tsx +191 -0
- package/src/global.d.ts +22 -0
- package/src/index.tsx +6 -0
- package/src/presets.test.ts +59 -0
- package/src/presets.ts +51 -0
- package/src/test-setup.ts +5 -0
- package/src/virtual.d.ts +14 -0
- package/tsconfig.json +6 -0
- package/vitest.config.ts +21 -0
package/src/entry.tsx
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import React, { useState, useEffect, useCallback } from "react";
|
|
2
|
+
import { createRoot } from "react-dom/client";
|
|
3
|
+
import { Shell } from "./Shell.js";
|
|
4
|
+
|
|
5
|
+
// @ts-ignore — resolved by vite-plugin-tome
|
|
6
|
+
import config from "virtual:tome/config";
|
|
7
|
+
// @ts-ignore — resolved by vite-plugin-tome
|
|
8
|
+
import { routes, navigation } from "virtual:tome/routes";
|
|
9
|
+
// @ts-ignore — resolved by vite-plugin-tome
|
|
10
|
+
import loadPageModule from "virtual:tome/page-loader";
|
|
11
|
+
// @ts-ignore — resolved by vite-plugin-tome
|
|
12
|
+
import docContext from "virtual:tome/doc-context";
|
|
13
|
+
|
|
14
|
+
// TOM-8: Built-in MDX components from @tomehq/components
|
|
15
|
+
// These are injected into every MDX page automatically
|
|
16
|
+
import {
|
|
17
|
+
Callout,
|
|
18
|
+
Tabs,
|
|
19
|
+
Card,
|
|
20
|
+
CardGroup,
|
|
21
|
+
Steps,
|
|
22
|
+
Accordion,
|
|
23
|
+
} from "@tomehq/components";
|
|
24
|
+
|
|
25
|
+
const MDX_COMPONENTS: Record<string, React.ComponentType<any>> = {
|
|
26
|
+
Callout,
|
|
27
|
+
Tabs,
|
|
28
|
+
Card,
|
|
29
|
+
CardGroup,
|
|
30
|
+
Steps,
|
|
31
|
+
Accordion,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// ── CONTENT STYLES ───────────────────────────────────────
|
|
35
|
+
const contentStyles = `
|
|
36
|
+
@import url('https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:wght@300;400;500;600;700&family=Cormorant+Garamond:ital,wght@0,300;0,400;0,600;0,700;1,300;1,400;1,700&family=Fira+Code:wght@400;500;600&display=swap');
|
|
37
|
+
|
|
38
|
+
.tome-content h2 { font-family: var(--font-body); font-size: 1.35em; font-weight: 600; margin-top: 2em; margin-bottom: 0.5em; display: flex; align-items: center; gap: 10px; letter-spacing: 0.01em; }
|
|
39
|
+
.tome-content h2::before { content: "#"; font-family: var(--font-heading); font-size: 1.2em; font-weight: 300; font-style: italic; color: var(--ac); opacity: 0.5; }
|
|
40
|
+
.tome-content h3 { font-family: var(--font-body); font-size: 1.15em; font-weight: 600; margin-top: 1.5em; margin-bottom: 0.5em; }
|
|
41
|
+
.tome-content h4 { font-family: var(--font-body); font-size: 1.05em; font-weight: 600; margin-top: 1.2em; margin-bottom: 0.5em; }
|
|
42
|
+
.tome-content p { color: var(--tx2); line-height: 1.8; margin-bottom: 1em; font-size: 14.5px; }
|
|
43
|
+
.tome-content a { color: var(--ac); text-decoration: none; }
|
|
44
|
+
.tome-content a:hover { text-decoration: underline; }
|
|
45
|
+
.tome-content .heading-anchor { display: none; }
|
|
46
|
+
.tome-content ul, .tome-content ol { color: var(--tx2); padding-left: 1.5em; margin-bottom: 1em; }
|
|
47
|
+
.tome-content li { margin-bottom: 0.3em; line-height: 1.7; }
|
|
48
|
+
.tome-content code { font-family: var(--font-code); font-size: 0.88em; background: var(--cdBg); padding: 0.15em 0.4em; border-radius: 2px; color: var(--ac); }
|
|
49
|
+
.tome-content pre { margin-bottom: 1.2em; border-radius: 2px; overflow-x: auto; border: 1px solid var(--bd); }
|
|
50
|
+
.tome-content pre code { background: none; padding: 1em 1.2em; display: block; font-size: 12.5px; line-height: 1.7; color: var(--cdTx); }
|
|
51
|
+
.tome-content blockquote { border-left: 3px solid var(--ac); padding: 0.5em 1em; margin: 1em 0; background: var(--acD); border-radius: 0 2px 2px 0; }
|
|
52
|
+
.tome-content blockquote p { color: var(--tx2); margin: 0; }
|
|
53
|
+
.tome-content table { width: 100%; border-collapse: collapse; margin-bottom: 1em; }
|
|
54
|
+
.tome-content th, .tome-content td { padding: 0.5em 0.8em; border: 1px solid var(--bd); text-align: left; font-size: 0.9em; }
|
|
55
|
+
.tome-content th { background: var(--sf); font-weight: 600; }
|
|
56
|
+
.tome-content img { max-width: 100%; border-radius: 2px; }
|
|
57
|
+
.tome-content hr { border: none; border-top: 1px solid var(--bd); margin: 2em 0; }
|
|
58
|
+
|
|
59
|
+
/* Selection style */
|
|
60
|
+
::selection { background: var(--acD); color: var(--ac); }
|
|
61
|
+
|
|
62
|
+
/* Scrollbar style */
|
|
63
|
+
::-webkit-scrollbar { width: 5px; height: 5px; }
|
|
64
|
+
::-webkit-scrollbar-track { background: transparent; }
|
|
65
|
+
::-webkit-scrollbar-thumb { background: var(--bd); border-radius: 10px; }
|
|
66
|
+
|
|
67
|
+
/* Grain overlay */
|
|
68
|
+
.tome-grain::before {
|
|
69
|
+
content: ""; position: fixed; inset: 0; z-index: 9999; pointer-events: none;
|
|
70
|
+
opacity: .35;
|
|
71
|
+
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.04'/%3E%3C/svg%3E");
|
|
72
|
+
background-repeat: repeat; background-size: 256px;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* Shiki dual-theme support */
|
|
76
|
+
.shiki { background: var(--cdBg) !important; }
|
|
77
|
+
html.dark .shiki .shiki-light { display: none; }
|
|
78
|
+
html.light .shiki .shiki-dark { display: none; }
|
|
79
|
+
`;
|
|
80
|
+
|
|
81
|
+
// ── PAGE TYPES ────────────────────────────────────────────
|
|
82
|
+
interface HtmlPage {
|
|
83
|
+
isMdx: false;
|
|
84
|
+
html: string;
|
|
85
|
+
frontmatter: { title: string; description?: string };
|
|
86
|
+
headings: Array<{ depth: number; text: string; id: string }>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
interface MdxPage {
|
|
90
|
+
isMdx: true;
|
|
91
|
+
component: React.ComponentType<{ components?: Record<string, React.ComponentType> }>;
|
|
92
|
+
frontmatter: { title: string; description?: string };
|
|
93
|
+
headings: Array<{ depth: number; text: string; id: string }>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
type LoadedPage = HtmlPage | MdxPage;
|
|
97
|
+
|
|
98
|
+
// ── PAGE LOADER ──────────────────────────────────────────
|
|
99
|
+
async function loadPage(id: string): Promise<LoadedPage | null> {
|
|
100
|
+
try {
|
|
101
|
+
const route = routes.find((r: any) => r.id === id);
|
|
102
|
+
const mod = await loadPageModule(id);
|
|
103
|
+
|
|
104
|
+
if (route?.isMdx && mod.meta) {
|
|
105
|
+
// TOM-8: MDX page — mod.default is the React component
|
|
106
|
+
return {
|
|
107
|
+
isMdx: true,
|
|
108
|
+
component: mod.default,
|
|
109
|
+
frontmatter: mod.meta.frontmatter,
|
|
110
|
+
headings: mod.meta.headings,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Regular .md page — mod.default is { html, frontmatter, headings }
|
|
115
|
+
if (!mod.default) return null;
|
|
116
|
+
return { isMdx: false, ...mod.default };
|
|
117
|
+
} catch (err) {
|
|
118
|
+
console.error(`Failed to load page: ${id}`, err);
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ── APP ──────────────────────────────────────────────────
|
|
124
|
+
function App() {
|
|
125
|
+
const [currentPageId, setCurrentPageId] = useState(() => {
|
|
126
|
+
const hash = window.location.hash.slice(1);
|
|
127
|
+
if (hash && routes.some((r: any) => r.id === hash)) return hash;
|
|
128
|
+
return routes[0]?.id || "index";
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const [pageData, setPageData] = useState<LoadedPage | null>(null);
|
|
132
|
+
const [loading, setLoading] = useState(true);
|
|
133
|
+
|
|
134
|
+
const navigateTo = useCallback(async (id: string) => {
|
|
135
|
+
setLoading(true);
|
|
136
|
+
setCurrentPageId(id);
|
|
137
|
+
window.location.hash = id;
|
|
138
|
+
const data = await loadPage(id);
|
|
139
|
+
setPageData(data);
|
|
140
|
+
setLoading(false);
|
|
141
|
+
}, []);
|
|
142
|
+
|
|
143
|
+
useEffect(() => { navigateTo(currentPageId); }, []);
|
|
144
|
+
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
const onHashChange = () => {
|
|
147
|
+
const hash = window.location.hash.slice(1);
|
|
148
|
+
// Only navigate if hash matches a known route ID (ignore heading anchors)
|
|
149
|
+
if (hash && hash !== currentPageId && routes.some((r: any) => r.id === hash)) {
|
|
150
|
+
navigateTo(hash);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
window.addEventListener("hashchange", onHashChange);
|
|
154
|
+
return () => window.removeEventListener("hashchange", onHashChange);
|
|
155
|
+
}, [currentPageId, navigateTo]);
|
|
156
|
+
|
|
157
|
+
const allPages = routes.map((r: any) => ({
|
|
158
|
+
id: r.id,
|
|
159
|
+
title: r.frontmatter.title,
|
|
160
|
+
description: r.frontmatter.description,
|
|
161
|
+
}));
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<>
|
|
165
|
+
<style>{contentStyles}</style>
|
|
166
|
+
<Shell
|
|
167
|
+
config={config}
|
|
168
|
+
navigation={navigation}
|
|
169
|
+
currentPageId={currentPageId}
|
|
170
|
+
pageHtml={!pageData?.isMdx ? (loading ? "<p>Loading...</p>" : pageData?.html || "<p>Page not found</p>") : undefined}
|
|
171
|
+
pageComponent={pageData?.isMdx ? pageData.component : undefined}
|
|
172
|
+
mdxComponents={MDX_COMPONENTS}
|
|
173
|
+
pageTitle={pageData?.frontmatter.title || (loading ? "Loading..." : "Not Found")}
|
|
174
|
+
pageDescription={pageData?.frontmatter.description}
|
|
175
|
+
headings={pageData?.headings || []}
|
|
176
|
+
onNavigate={navigateTo}
|
|
177
|
+
allPages={allPages}
|
|
178
|
+
docContext={docContext}
|
|
179
|
+
/>
|
|
180
|
+
</>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ── MOUNT ────────────────────────────────────────────────
|
|
185
|
+
const container = document.getElementById("tome-root");
|
|
186
|
+
if (container) {
|
|
187
|
+
const root = createRoot(container);
|
|
188
|
+
root.render(<App />);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export default App;
|
package/src/global.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
declare const __TOME_AI_API_KEY__: string | undefined;
|
|
2
|
+
|
|
3
|
+
declare module "virtual:tome/config" {
|
|
4
|
+
const config: any;
|
|
5
|
+
export default config;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
declare module "virtual:tome/routes" {
|
|
9
|
+
export const routes: any[];
|
|
10
|
+
export const navigation: any[];
|
|
11
|
+
export const versions: any;
|
|
12
|
+
export const i18n: any;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
declare module "virtual:tome/page-loader" {
|
|
16
|
+
export default function loadPageModule(id: string): Promise<any>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
declare module "virtual:tome/doc-context" {
|
|
20
|
+
const docContext: Array<{ id: string; title: string; content: string }>;
|
|
21
|
+
export default docContext;
|
|
22
|
+
}
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { Shell } from "./Shell.js";
|
|
2
|
+
export { AiChat } from "./AiChat.js";
|
|
3
|
+
export type { AiChatProps } from "./AiChat.js";
|
|
4
|
+
export { default as App } from "./entry.js";
|
|
5
|
+
export { THEME_PRESETS } from "./presets.js";
|
|
6
|
+
export type { ThemeTokens, ThemePreset, PresetName } from "./presets.js";
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { THEME_PRESETS } from "./presets.js";
|
|
3
|
+
import type { PresetName } from "./presets.js";
|
|
4
|
+
|
|
5
|
+
const TOKEN_KEYS = [
|
|
6
|
+
"bg", "sf", "sfH", "bd",
|
|
7
|
+
"tx", "tx2", "txM",
|
|
8
|
+
"ac", "acD", "acT",
|
|
9
|
+
"cdBg", "cdTx", "sbBg", "hdBg",
|
|
10
|
+
] as const;
|
|
11
|
+
|
|
12
|
+
const FONT_KEYS = ["heading", "body", "code"] as const;
|
|
13
|
+
|
|
14
|
+
const PRESET_NAMES: PresetName[] = ["amber", "editorial"];
|
|
15
|
+
|
|
16
|
+
describe("THEME_PRESETS", () => {
|
|
17
|
+
it("contains both amber and editorial presets", () => {
|
|
18
|
+
expect(THEME_PRESETS).toHaveProperty("amber");
|
|
19
|
+
expect(THEME_PRESETS).toHaveProperty("editorial");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
for (const name of PRESET_NAMES) {
|
|
23
|
+
describe(`${name} preset`, () => {
|
|
24
|
+
it("has dark and light token sets", () => {
|
|
25
|
+
expect(THEME_PRESETS[name]).toHaveProperty("dark");
|
|
26
|
+
expect(THEME_PRESETS[name]).toHaveProperty("light");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
for (const mode of ["dark", "light"] as const) {
|
|
30
|
+
describe(`${mode} tokens`, () => {
|
|
31
|
+
it(`has all 14 token properties`, () => {
|
|
32
|
+
const tokens = THEME_PRESETS[name][mode];
|
|
33
|
+
for (const key of TOKEN_KEYS) {
|
|
34
|
+
expect(tokens).toHaveProperty(key);
|
|
35
|
+
expect(typeof tokens[key]).toBe("string");
|
|
36
|
+
expect(tokens[key].length).toBeGreaterThan(0);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("has exactly 14 token properties", () => {
|
|
41
|
+
const tokens = THEME_PRESETS[name][mode];
|
|
42
|
+
expect(Object.keys(tokens)).toHaveLength(14);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
describe("fonts", () => {
|
|
48
|
+
it("has heading, body, and code font families defined", () => {
|
|
49
|
+
const fonts = THEME_PRESETS[name].fonts;
|
|
50
|
+
for (const key of FONT_KEYS) {
|
|
51
|
+
expect(fonts).toHaveProperty(key);
|
|
52
|
+
expect(typeof fonts[key]).toBe("string");
|
|
53
|
+
expect(fonts[key].length).toBeGreaterThan(0);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
});
|
package/src/presets.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// ── Theme Preset Types ────────────────────────────────────
|
|
2
|
+
|
|
3
|
+
export interface ThemeTokens {
|
|
4
|
+
bg: string; sf: string; sfH: string; bd: string;
|
|
5
|
+
tx: string; tx2: string; txM: string;
|
|
6
|
+
ac: string; acD: string; acT: string;
|
|
7
|
+
cdBg: string; cdTx: string; sbBg: string; hdBg: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ThemePreset {
|
|
11
|
+
dark: ThemeTokens;
|
|
12
|
+
light: ThemeTokens;
|
|
13
|
+
fonts: { heading: string; body: string; code: string };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// ── Theme Presets ─────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
export const THEME_PRESETS = {
|
|
19
|
+
amber: {
|
|
20
|
+
dark: {
|
|
21
|
+
bg:"#09090b",sf:"#111114",sfH:"#18181c",bd:"#1e1e24",
|
|
22
|
+
tx:"#e4e4e7",tx2:"#a1a1aa",txM:"#919199",
|
|
23
|
+
ac:"#e8a845",acD:"rgba(232,168,69,0.12)",acT:"#fbbf24",
|
|
24
|
+
cdBg:"#0c0c0f",cdTx:"#c4c4cc",sbBg:"#0c0c0e",hdBg:"rgba(9,9,11,0.85)",
|
|
25
|
+
},
|
|
26
|
+
light: {
|
|
27
|
+
bg:"#fafaf9",sf:"#ffffff",sfH:"#f5f5f4",bd:"#e7e5e4",
|
|
28
|
+
tx:"#1c1917",tx2:"#57534e",txM:"#706b66",
|
|
29
|
+
ac:"#96640a",acD:"rgba(150,100,10,0.08)",acT:"#7a5208",
|
|
30
|
+
cdBg:"#f5f3f0",cdTx:"#2c2520",sbBg:"#f5f5f3",hdBg:"rgba(250,250,249,0.85)",
|
|
31
|
+
},
|
|
32
|
+
fonts: { heading: "Instrument Serif", body: "DM Sans", code: "JetBrains Mono" },
|
|
33
|
+
},
|
|
34
|
+
editorial: {
|
|
35
|
+
dark: {
|
|
36
|
+
bg:"#080c1f",sf:"#0e1333",sfH:"#141940",bd:"#1a2050",
|
|
37
|
+
tx:"#e8e6f0",tx2:"#b5b1c8",txM:"#9490ae",
|
|
38
|
+
ac:"#ff6b4a",acD:"rgba(255,107,74,0.1)",acT:"#ff8a70",
|
|
39
|
+
cdBg:"#0a0e27",cdTx:"#b8b4cc",sbBg:"#0a0e27",hdBg:"rgba(8,12,31,0.9)",
|
|
40
|
+
},
|
|
41
|
+
light: {
|
|
42
|
+
bg:"#f6f4f0",sf:"#ffffff",sfH:"#eeece6",bd:"#ddd9d0",
|
|
43
|
+
tx:"#1a1716",tx2:"#4a443e",txM:"#706960",
|
|
44
|
+
ac:"#b83d22",acD:"rgba(184,61,34,0.07)",acT:"#9c3019",
|
|
45
|
+
cdBg:"#edeae4",cdTx:"#3a3530",sbBg:"#f0ede8",hdBg:"rgba(246,244,240,0.92)",
|
|
46
|
+
},
|
|
47
|
+
fonts: { heading: "Cormorant Garamond", body: "Bricolage Grotesque", code: "Fira Code" },
|
|
48
|
+
},
|
|
49
|
+
} as const satisfies Record<string, ThemePreset>;
|
|
50
|
+
|
|
51
|
+
export type PresetName = keyof typeof THEME_PRESETS;
|
package/src/virtual.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
declare module "virtual:tome/config" {
|
|
2
|
+
const config: import("@tomehq/core").TomeConfig;
|
|
3
|
+
export default config;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
declare module "virtual:tome/routes" {
|
|
7
|
+
export const routes: Array<{
|
|
8
|
+
id: string;
|
|
9
|
+
urlPath: string;
|
|
10
|
+
frontmatter: import("@tomehq/core").PageFrontmatter;
|
|
11
|
+
isMdx: boolean;
|
|
12
|
+
}>;
|
|
13
|
+
export const navigation: import("@tomehq/core").NavigationGroup[];
|
|
14
|
+
}
|
package/tsconfig.json
ADDED
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { defineConfig } from "vitest/config";
|
|
2
|
+
import { resolve } from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
|
|
5
|
+
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
|
6
|
+
|
|
7
|
+
export default defineConfig({
|
|
8
|
+
root: __dirname,
|
|
9
|
+
test: {
|
|
10
|
+
name: "theme",
|
|
11
|
+
environment: "jsdom",
|
|
12
|
+
globals: true,
|
|
13
|
+
include: ["src/**/*.test.tsx", "src/**/*.test.ts"],
|
|
14
|
+
setupFiles: [resolve(__dirname, "src/test-setup.ts")],
|
|
15
|
+
coverage: {
|
|
16
|
+
provider: "v8",
|
|
17
|
+
include: ["src/**/*.tsx", "src/**/*.ts"],
|
|
18
|
+
exclude: ["src/**/*.test.*", "src/test-setup.ts", "src/virtual.d.ts"],
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
});
|