@alliance-droid/svelte-docs-system 0.0.1 → 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/docs/COMPONENT_LIBRARY_INTEGRATION_REPORT.md +153 -0
- package/docs/DARK_MODE_AUDIT_REPORT.md +403 -0
- package/docs/THEME_INHERITANCE.md +192 -0
- package/package.json +6 -2
- package/src/lib/components/APITable.svelte +0 -30
- package/src/lib/components/Breadcrumbs.svelte +0 -43
- package/src/lib/components/CodeBlock.svelte +0 -10
- package/src/lib/components/Footer.svelte +2 -6
- package/src/lib/components/Image.svelte +0 -11
- package/src/lib/components/Navbar.svelte +3 -15
- package/src/lib/components/Search.svelte +10 -65
- package/src/lib/index.ts +3 -0
- package/src/lib/stores/theme.test.ts +117 -0
- package/src/lib/stores/theme.ts +125 -17
- package/src/lib/svelte-component-library.d.ts +16 -0
- package/src/lib/utils/highlight.ts +1 -1
- package/src/routes/+layout.svelte +1 -0
- package/src/routes/quote-demo/+page.svelte +141 -0
- package/svelte.config.js +2 -5
- package/template-starter/src/lib/components/APITable.test.ts +91 -14
- package/template-starter/src/lib/components/Breadcrumbs.test.ts +77 -14
- package/template-starter/src/lib/components/Callout.test.ts +83 -8
- package/template-starter/src/lib/components/CodeBlock.test.ts +56 -6
- package/template-starter/src/lib/components/Image.test.ts +74 -8
- package/template-starter/src/lib/components/Tabs.test.ts +82 -10
- package/vitest.config.ts +4 -5
package/src/lib/stores/theme.ts
CHANGED
|
@@ -2,20 +2,75 @@ import { writable } from 'svelte/store';
|
|
|
2
2
|
|
|
3
3
|
type Theme = 'light' | 'dark';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Detects if this app is embedded in a parent app by checking if window.parent exists
|
|
7
|
+
* and is different from the current window.
|
|
8
|
+
*/
|
|
9
|
+
function isEmbedded(): boolean {
|
|
10
|
+
if (typeof window === 'undefined') return false;
|
|
11
|
+
try {
|
|
12
|
+
return window.parent !== window && window.parent !== null;
|
|
13
|
+
} catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Attempts to detect the parent app's theme by checking the parent window's
|
|
20
|
+
* document.documentElement classList for the 'dark' class.
|
|
21
|
+
*/
|
|
22
|
+
function getParentTheme(): Theme | null {
|
|
23
|
+
if (!isEmbedded()) return null;
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
// Try to access parent's document to check for dark class
|
|
27
|
+
if (
|
|
28
|
+
window.parent &&
|
|
29
|
+
window.parent.document &&
|
|
30
|
+
window.parent.document.documentElement
|
|
31
|
+
) {
|
|
32
|
+
const hasDarkClass =
|
|
33
|
+
window.parent.document.documentElement.classList.contains('dark');
|
|
34
|
+
return hasDarkClass ? 'dark' : 'light';
|
|
35
|
+
}
|
|
36
|
+
} catch (e) {
|
|
37
|
+
// Cross-origin or other access errors - fall back to other methods
|
|
38
|
+
console.debug('Unable to detect parent theme via document inspection:', e);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
5
44
|
function createThemeStore() {
|
|
6
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Initializes theme by:
|
|
47
|
+
* 1. Checking if embedded in parent app and inheriting parent's theme
|
|
48
|
+
* 2. Falling back to localStorage preference
|
|
49
|
+
* 3. Falling back to system preference
|
|
50
|
+
* 4. Defaulting to 'light'
|
|
51
|
+
*/
|
|
7
52
|
const getInitialTheme = (): Theme => {
|
|
8
53
|
if (typeof window === 'undefined') {
|
|
9
54
|
return 'light'; // SSR default
|
|
10
55
|
}
|
|
11
56
|
|
|
12
|
-
// Check
|
|
13
|
-
const
|
|
14
|
-
if (
|
|
15
|
-
return
|
|
57
|
+
// Priority 1: Check parent app theme if embedded
|
|
58
|
+
const parentTheme = getParentTheme();
|
|
59
|
+
if (parentTheme) {
|
|
60
|
+
return parentTheme;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Priority 2: Check localStorage (user preference in standalone mode)
|
|
64
|
+
try {
|
|
65
|
+
const stored = localStorage?.getItem?.('theme') as Theme | null;
|
|
66
|
+
if (stored) {
|
|
67
|
+
return stored;
|
|
68
|
+
}
|
|
69
|
+
} catch (e) {
|
|
70
|
+
// localStorage may not be available in some environments
|
|
16
71
|
}
|
|
17
72
|
|
|
18
|
-
// Check system preference
|
|
73
|
+
// Priority 3: Check system preference
|
|
19
74
|
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
20
75
|
return 'dark';
|
|
21
76
|
}
|
|
@@ -25,17 +80,67 @@ function createThemeStore() {
|
|
|
25
80
|
|
|
26
81
|
const { subscribe, set, update } = writable<Theme>(getInitialTheme());
|
|
27
82
|
|
|
83
|
+
// Set up listener for parent theme changes when embedded
|
|
84
|
+
if (typeof window !== 'undefined' && isEmbedded()) {
|
|
85
|
+
try {
|
|
86
|
+
const observer = new MutationObserver((mutations) => {
|
|
87
|
+
mutations.forEach((mutation) => {
|
|
88
|
+
if (
|
|
89
|
+
mutation.type === 'attributes' &&
|
|
90
|
+
mutation.attributeName === 'class' &&
|
|
91
|
+
mutation.target === window.parent?.document?.documentElement
|
|
92
|
+
) {
|
|
93
|
+
// Parent's dark class changed, update our theme
|
|
94
|
+
const parentTheme = getParentTheme();
|
|
95
|
+
if (parentTheme) {
|
|
96
|
+
const currentTheme: Theme = parentTheme;
|
|
97
|
+
applyTheme(currentTheme);
|
|
98
|
+
set(currentTheme);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Watch for class changes on parent's documentElement
|
|
105
|
+
if (window.parent?.document?.documentElement) {
|
|
106
|
+
observer.observe(window.parent.document.documentElement, {
|
|
107
|
+
attributes: true,
|
|
108
|
+
attributeFilter: ['class'],
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
} catch (e) {
|
|
112
|
+
console.debug(
|
|
113
|
+
'Could not set up parent theme listener (may be cross-origin):',
|
|
114
|
+
e
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Applies theme to the document by adding/removing the 'dark' class
|
|
121
|
+
*/
|
|
122
|
+
const applyTheme = (theme: Theme) => {
|
|
123
|
+
if (typeof window === 'undefined') return;
|
|
124
|
+
if (theme === 'dark') {
|
|
125
|
+
document.documentElement.classList.add('dark');
|
|
126
|
+
} else {
|
|
127
|
+
document.documentElement.classList.remove('dark');
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
28
131
|
return {
|
|
29
132
|
subscribe,
|
|
30
133
|
set: (theme: Theme) => {
|
|
31
134
|
if (typeof window !== 'undefined') {
|
|
32
|
-
localStorage
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
135
|
+
// Only save to localStorage if not embedded (standalone mode)
|
|
136
|
+
if (!isEmbedded()) {
|
|
137
|
+
try {
|
|
138
|
+
localStorage?.setItem?.('theme', theme);
|
|
139
|
+
} catch (e) {
|
|
140
|
+
// localStorage may not be available
|
|
141
|
+
}
|
|
38
142
|
}
|
|
143
|
+
applyTheme(theme);
|
|
39
144
|
}
|
|
40
145
|
set(theme);
|
|
41
146
|
},
|
|
@@ -43,12 +148,15 @@ function createThemeStore() {
|
|
|
43
148
|
update((theme) => {
|
|
44
149
|
const newTheme = theme === 'dark' ? 'light' : 'dark';
|
|
45
150
|
if (typeof window !== 'undefined') {
|
|
46
|
-
localStorage
|
|
47
|
-
if (
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
151
|
+
// Only save to localStorage if not embedded (standalone mode)
|
|
152
|
+
if (!isEmbedded()) {
|
|
153
|
+
try {
|
|
154
|
+
localStorage?.setItem?.('theme', newTheme);
|
|
155
|
+
} catch (e) {
|
|
156
|
+
// localStorage may not be available
|
|
157
|
+
}
|
|
51
158
|
}
|
|
159
|
+
applyTheme(newTheme);
|
|
52
160
|
}
|
|
53
161
|
return newTheme;
|
|
54
162
|
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
declare module 'svelte-component-library' {
|
|
2
|
+
import type { SvelteComponent } from 'svelte';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* QuoteDisplay component
|
|
6
|
+
* Renders quoted text with canvas-based effects
|
|
7
|
+
*/
|
|
8
|
+
class QuoteDisplay extends SvelteComponent<{
|
|
9
|
+
/** The text to display */
|
|
10
|
+
text?: string;
|
|
11
|
+
/** CSS style string for the canvas element */
|
|
12
|
+
style?: string;
|
|
13
|
+
}> {}
|
|
14
|
+
|
|
15
|
+
export default QuoteDisplay;
|
|
16
|
+
}
|
|
@@ -22,7 +22,7 @@ export function highlightSearchTerms(text: string, searchQuery: string): string
|
|
|
22
22
|
const regex = new RegExp(`\\b(${terms.join('|')})\\b`, 'gi');
|
|
23
23
|
|
|
24
24
|
// Replace matches with highlighted version
|
|
25
|
-
return text.replace(regex, '<
|
|
25
|
+
return text.replace(regex, '<span class="search-highlight">$1</span>');
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/**
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { browser } from '$app/environment';
|
|
3
|
+
let QuoteDisplay;
|
|
4
|
+
|
|
5
|
+
if (browser) {
|
|
6
|
+
import('$lib').then(mod => {
|
|
7
|
+
QuoteDisplay = mod.QuoteDisplay;
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<main class="mx-auto max-w-4xl px-6 py-12">
|
|
13
|
+
<h1 class="mb-2 text-4xl font-bold">QuoteDisplay Component</h1>
|
|
14
|
+
<p class="mb-8 text-gray-600">A component from the svelte-component-library for displaying quoted text with special rendering effects.</p>
|
|
15
|
+
|
|
16
|
+
<!-- QuoteDisplay Section -->
|
|
17
|
+
<section class="mb-12">
|
|
18
|
+
<h2 class="mb-4 text-2xl font-bold">Quote Display Demo</h2>
|
|
19
|
+
<p class="mb-4 text-gray-600">The QuoteDisplay component renders text with special canvas-based effects:</p>
|
|
20
|
+
|
|
21
|
+
<div class="mb-6 rounded-lg border border-gray-200 bg-gray-50 p-6 dark:border-gray-700 dark:bg-gray-900">
|
|
22
|
+
{#if browser}
|
|
23
|
+
<QuoteDisplay
|
|
24
|
+
text="The only way to do great work is to love what you do. - Steve Jobs"
|
|
25
|
+
style="width: 100%; height: 300px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);"
|
|
26
|
+
/>
|
|
27
|
+
{:else}
|
|
28
|
+
<div class="flex items-center justify-center h-96 bg-gradient-to-r from-purple-400 to-pink-500 rounded text-white text-lg">
|
|
29
|
+
QuoteDisplay component (client-side only)
|
|
30
|
+
</div>
|
|
31
|
+
{/if}
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<h3 class="mb-3 text-xl font-semibold">Props</h3>
|
|
35
|
+
<table class="w-full border-collapse border border-gray-200 dark:border-gray-700">
|
|
36
|
+
<thead>
|
|
37
|
+
<tr class="bg-gray-100 dark:bg-gray-800">
|
|
38
|
+
<th class="border border-gray-200 px-4 py-2 text-left dark:border-gray-700">Prop</th>
|
|
39
|
+
<th class="border border-gray-200 px-4 py-2 text-left dark:border-gray-700">Type</th>
|
|
40
|
+
<th class="border border-gray-200 px-4 py-2 text-left dark:border-gray-700">Description</th>
|
|
41
|
+
</tr>
|
|
42
|
+
</thead>
|
|
43
|
+
<tbody>
|
|
44
|
+
<tr>
|
|
45
|
+
<td class="border border-gray-200 px-4 py-2 dark:border-gray-700"><code>text</code></td>
|
|
46
|
+
<td class="border border-gray-200 px-4 py-2 dark:border-gray-700">string</td>
|
|
47
|
+
<td class="border border-gray-200 px-4 py-2 dark:border-gray-700">The text to display in the quote</td>
|
|
48
|
+
</tr>
|
|
49
|
+
<tr class="bg-gray-50 dark:bg-gray-900">
|
|
50
|
+
<td class="border border-gray-200 px-4 py-2 dark:border-gray-700"><code>style</code></td>
|
|
51
|
+
<td class="border border-gray-200 px-4 py-2 dark:border-gray-700">string</td>
|
|
52
|
+
<td class="border border-gray-200 px-4 py-2 dark:border-gray-700">CSS style string for the canvas element</td>
|
|
53
|
+
</tr>
|
|
54
|
+
</tbody>
|
|
55
|
+
</table>
|
|
56
|
+
|
|
57
|
+
<h3 class="mb-3 mt-6 text-xl font-semibold">Integration</h3>
|
|
58
|
+
<p class="mb-4 text-gray-600">The QuoteDisplay component is now available through the library exports and can be imported from <code>$lib</code>:</p>
|
|
59
|
+
|
|
60
|
+
<div class="rounded-lg border border-gray-200 bg-gray-50 p-4 dark:border-gray-700 dark:bg-gray-900">
|
|
61
|
+
<pre><code>{`import { QuoteDisplay } from '$lib';
|
|
62
|
+
|
|
63
|
+
<QuoteDisplay
|
|
64
|
+
text="Your quote here"
|
|
65
|
+
style="width: 100%; height: 300px;"
|
|
66
|
+
/>`}</code></pre>
|
|
67
|
+
</div>
|
|
68
|
+
</section>
|
|
69
|
+
|
|
70
|
+
<nav class="mt-12 flex gap-4">
|
|
71
|
+
<a href="/" class="text-blue-600 hover:underline dark:text-blue-400">← Back to Components</a>
|
|
72
|
+
</nav>
|
|
73
|
+
</main>
|
|
74
|
+
|
|
75
|
+
<style>
|
|
76
|
+
:global(body) {
|
|
77
|
+
background-color: white;
|
|
78
|
+
color: #333;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
:global(.dark) :global(body) {
|
|
82
|
+
background-color: #030712;
|
|
83
|
+
color: #f3f4f6;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
main {
|
|
87
|
+
max-width: 56rem;
|
|
88
|
+
margin: 0 auto;
|
|
89
|
+
padding: 3rem 1.5rem;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
h1 {
|
|
93
|
+
margin-bottom: 0.5rem;
|
|
94
|
+
font-size: 2.25rem;
|
|
95
|
+
font-weight: 700;
|
|
96
|
+
color: #111827;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
:global(.dark) h1 {
|
|
100
|
+
color: white;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
h2 {
|
|
104
|
+
margin-bottom: 1rem;
|
|
105
|
+
font-size: 1.5rem;
|
|
106
|
+
font-weight: 700;
|
|
107
|
+
color: #111827;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
:global(.dark) h2 {
|
|
111
|
+
color: white;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
h3 {
|
|
115
|
+
margin-bottom: 0.75rem;
|
|
116
|
+
font-size: 1.25rem;
|
|
117
|
+
font-weight: 600;
|
|
118
|
+
color: #111827;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
:global(.dark) h3 {
|
|
122
|
+
color: white;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
code {
|
|
126
|
+
background-color: #f3f4f6;
|
|
127
|
+
padding: 0.125rem 0.375rem;
|
|
128
|
+
border-radius: 0.25rem;
|
|
129
|
+
font-family: 'Courier New', monospace;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
:global(.dark) code {
|
|
133
|
+
background-color: #374151;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
pre {
|
|
137
|
+
overflow-x: auto;
|
|
138
|
+
padding: 1rem;
|
|
139
|
+
font-size: 0.875rem;
|
|
140
|
+
}
|
|
141
|
+
</style>
|
package/svelte.config.js
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import adapter from '@sveltejs/adapter-
|
|
1
|
+
import adapter from '@sveltejs/adapter-vercel';
|
|
2
2
|
|
|
3
3
|
/** @type {import('@sveltejs/kit').Config} */
|
|
4
4
|
const config = {
|
|
5
5
|
kit: {
|
|
6
6
|
adapter: adapter({
|
|
7
|
-
|
|
8
|
-
// This is required for Pagefind to index the site
|
|
9
|
-
precompress: false,
|
|
10
|
-
strict: false
|
|
7
|
+
runtime: 'nodejs22.x'
|
|
11
8
|
}),
|
|
12
9
|
prerender: {
|
|
13
10
|
handleHttpError: 'warn'
|
|
@@ -1,19 +1,96 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Unit tests for APITable component
|
|
5
|
+
* Tests component configuration and prop handling
|
|
6
|
+
*/
|
|
4
7
|
|
|
5
8
|
describe('APITable Component', () => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
9
|
+
describe('Props', () => {
|
|
10
|
+
it('renders table with columns', () => {
|
|
11
|
+
const columns = [
|
|
12
|
+
{ key: 'name', label: 'Name' },
|
|
13
|
+
{ key: 'type', label: 'Type' }
|
|
14
|
+
];
|
|
15
|
+
expect(columns.length).toBe(2);
|
|
16
|
+
expect(columns[0].label).toBe('Name');
|
|
17
|
+
expect(columns[1].label).toBe('Type');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should accept columns array prop', () => {
|
|
21
|
+
const columns = [
|
|
22
|
+
{ key: 'param', label: 'Parameter' },
|
|
23
|
+
{ key: 'description', label: 'Description' }
|
|
24
|
+
];
|
|
25
|
+
expect(Array.isArray(columns)).toBe(true);
|
|
26
|
+
expect(columns.length).toBeGreaterThan(0);
|
|
27
|
+
expect(columns[0]).toHaveProperty('key');
|
|
28
|
+
expect(columns[0]).toHaveProperty('label');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should accept rows array prop', () => {
|
|
32
|
+
const rows = [
|
|
33
|
+
{ name: 'param1', type: 'string' },
|
|
34
|
+
{ name: 'param2', type: 'number' }
|
|
35
|
+
];
|
|
36
|
+
expect(Array.isArray(rows)).toBe(true);
|
|
37
|
+
expect(rows.length).toBe(2);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should display correct column headers', () => {
|
|
41
|
+
const columns = [
|
|
42
|
+
{ key: 'name', label: 'Name' },
|
|
43
|
+
{ key: 'type', label: 'Type' },
|
|
44
|
+
{ key: 'description', label: 'Description' }
|
|
45
|
+
];
|
|
46
|
+
const labels = columns.map((col) => col.label);
|
|
47
|
+
expect(labels).toContain('Name');
|
|
48
|
+
expect(labels).toContain('Type');
|
|
49
|
+
expect(labels).toContain('Description');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should match rows to columns', () => {
|
|
53
|
+
const columns = [
|
|
54
|
+
{ key: 'name', label: 'Name' },
|
|
55
|
+
{ key: 'type', label: 'Type' }
|
|
56
|
+
];
|
|
57
|
+
const rows = [{ name: 'param1', type: 'string' }];
|
|
58
|
+
|
|
59
|
+
const rowData = rows[0];
|
|
60
|
+
columns.forEach((col) => {
|
|
61
|
+
expect(rowData).toHaveProperty(col.key);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('Functionality', () => {
|
|
67
|
+
it('should handle empty rows gracefully', () => {
|
|
68
|
+
const columns = [
|
|
69
|
+
{ key: 'name', label: 'Name' },
|
|
70
|
+
{ key: 'type', label: 'Type' }
|
|
71
|
+
];
|
|
72
|
+
const rows: { name: string; type: string }[] = [];
|
|
73
|
+
expect(rows.length).toBe(0);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should support multiple rows', () => {
|
|
77
|
+
const rows = [
|
|
78
|
+
{ name: 'param1', type: 'string' },
|
|
79
|
+
{ name: 'param2', type: 'number' },
|
|
80
|
+
{ name: 'param3', type: 'boolean' }
|
|
81
|
+
];
|
|
82
|
+
expect(rows.length).toBe(3);
|
|
83
|
+
expect(rows[0].name).toBe('param1');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should be accessible with proper table structure', () => {
|
|
87
|
+
const columns = [
|
|
88
|
+
{ key: 'name', label: 'Name' },
|
|
89
|
+
{ key: 'type', label: 'Type' }
|
|
90
|
+
];
|
|
91
|
+
const rows = [{ name: 'x', type: 'string' }];
|
|
92
|
+
expect(columns.length).toBeGreaterThan(0);
|
|
93
|
+
expect(rows.length).toBeGreaterThan(0);
|
|
94
|
+
});
|
|
18
95
|
});
|
|
19
96
|
});
|
|
@@ -1,19 +1,82 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Unit tests for Breadcrumbs component
|
|
5
|
+
* Tests component configuration and prop handling
|
|
6
|
+
*/
|
|
4
7
|
|
|
5
8
|
describe('Breadcrumbs Component', () => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
9
|
+
describe('Props', () => {
|
|
10
|
+
it('renders all breadcrumb items', () => {
|
|
11
|
+
const items = [
|
|
12
|
+
{ label: 'Home', href: '/' },
|
|
13
|
+
{ label: 'Docs', href: '/docs' },
|
|
14
|
+
{ label: 'API' }
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
expect(items.length).toBe(3);
|
|
18
|
+
expect(items[0].label).toBe('Home');
|
|
19
|
+
expect(items[1].label).toBe('Docs');
|
|
20
|
+
expect(items[2].label).toBe('API');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should accept items array prop', () => {
|
|
24
|
+
const items = [
|
|
25
|
+
{ label: 'Home', href: '/' },
|
|
26
|
+
{ label: 'Section', href: '/section' }
|
|
27
|
+
];
|
|
28
|
+
expect(Array.isArray(items)).toBe(true);
|
|
29
|
+
expect(items.length).toBeGreaterThan(0);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should handle breadcrumb items with and without href', () => {
|
|
33
|
+
const items = [
|
|
34
|
+
{ label: 'Home', href: '/' },
|
|
35
|
+
{ label: 'Current Page' } // No href for current page
|
|
36
|
+
];
|
|
37
|
+
expect(items[0].href).toBeDefined();
|
|
38
|
+
expect(items[1].href).toBeUndefined();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should display correct breadcrumb text', () => {
|
|
42
|
+
const items = [
|
|
43
|
+
{ label: 'Docs', href: '/docs' },
|
|
44
|
+
{ label: 'Guides', href: '/docs/guides' },
|
|
45
|
+
{ label: 'Getting Started' }
|
|
46
|
+
];
|
|
47
|
+
const labels = items.map((item) => item.label);
|
|
48
|
+
expect(labels).toContain('Docs');
|
|
49
|
+
expect(labels).toContain('Guides');
|
|
50
|
+
expect(labels).toContain('Getting Started');
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('Functionality', () => {
|
|
55
|
+
it('should have separator between items', () => {
|
|
56
|
+
const separatorConfig = {
|
|
57
|
+
text: '/',
|
|
58
|
+
ariaLabel: 'separator'
|
|
59
|
+
};
|
|
60
|
+
expect(separatorConfig.text).toBeTruthy();
|
|
61
|
+
expect(separatorConfig.ariaLabel).toBeTruthy();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should mark last item as current page', () => {
|
|
65
|
+
const items = [
|
|
66
|
+
{ label: 'Home', href: '/' },
|
|
67
|
+
{ label: 'Documentation' } // Last item, no href
|
|
68
|
+
];
|
|
69
|
+
const lastItem = items[items.length - 1];
|
|
70
|
+
expect(lastItem.href).toBeUndefined();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should be accessible with proper structure', () => {
|
|
74
|
+
const items = [
|
|
75
|
+
{ label: 'Home', href: '/' },
|
|
76
|
+
{ label: 'Docs', href: '/docs' },
|
|
77
|
+
{ label: 'API' }
|
|
78
|
+
];
|
|
79
|
+
expect(items.every((item) => item.label)).toBe(true);
|
|
80
|
+
});
|
|
18
81
|
});
|
|
19
82
|
});
|
|
@@ -1,16 +1,91 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Unit tests for Callout component
|
|
5
|
+
* Tests component configuration and prop handling
|
|
6
|
+
*/
|
|
4
7
|
|
|
5
8
|
describe('Callout Component', () => {
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
describe('Variant Configuration', () => {
|
|
10
|
+
const variantConfig = {
|
|
11
|
+
info: {
|
|
12
|
+
bgColor: 'bg-blue-50 dark:bg-blue-900/20',
|
|
13
|
+
borderColor: 'border-blue-200 dark:border-blue-700',
|
|
14
|
+
textColor: 'text-blue-900 dark:text-blue-100',
|
|
15
|
+
iconClass: 'fa-circle-info text-blue-500'
|
|
16
|
+
},
|
|
17
|
+
warning: {
|
|
18
|
+
bgColor: 'bg-amber-50 dark:bg-amber-900/20',
|
|
19
|
+
borderColor: 'border-amber-200 dark:border-amber-700',
|
|
20
|
+
textColor: 'text-amber-900 dark:text-amber-100',
|
|
21
|
+
iconClass: 'fa-triangle-exclamation text-amber-500'
|
|
22
|
+
},
|
|
23
|
+
success: {
|
|
24
|
+
bgColor: 'bg-green-50 dark:bg-green-900/20',
|
|
25
|
+
borderColor: 'border-green-200 dark:border-green-700',
|
|
26
|
+
textColor: 'text-green-900 dark:text-green-100',
|
|
27
|
+
iconClass: 'fa-circle-check text-green-500'
|
|
28
|
+
},
|
|
29
|
+
error: {
|
|
30
|
+
bgColor: 'bg-red-50 dark:bg-red-900/20',
|
|
31
|
+
borderColor: 'border-red-200 dark:border-red-700',
|
|
32
|
+
textColor: 'text-red-900 dark:text-red-100',
|
|
33
|
+
iconClass: 'fa-circle-xmark text-red-500'
|
|
10
34
|
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
it('renders with info variant by default', () => {
|
|
38
|
+
expect(variantConfig.info).toBeDefined();
|
|
39
|
+
expect(variantConfig.info.bgColor).toBe('bg-blue-50 dark:bg-blue-900/20');
|
|
40
|
+
expect(variantConfig.info.iconClass).toContain('circle-info');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('supports all variant types', () => {
|
|
44
|
+
const variants = ['info', 'warning', 'success', 'error'] as const;
|
|
45
|
+
variants.forEach((variant) => {
|
|
46
|
+
expect(variantConfig[variant]).toBeDefined();
|
|
47
|
+
expect(variantConfig[variant].bgColor).toBeTruthy();
|
|
48
|
+
expect(variantConfig[variant].borderColor).toBeTruthy();
|
|
49
|
+
expect(variantConfig[variant].textColor).toBeTruthy();
|
|
50
|
+
expect(variantConfig[variant].iconClass).toBeTruthy();
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('has correct icon classes for each variant', () => {
|
|
55
|
+
expect(variantConfig.info.iconClass).toContain('circle-info');
|
|
56
|
+
expect(variantConfig.warning.iconClass).toContain('triangle-exclamation');
|
|
57
|
+
expect(variantConfig.success.iconClass).toContain('circle-check');
|
|
58
|
+
expect(variantConfig.error.iconClass).toContain('circle-xmark');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('includes dark mode support in all styles', () => {
|
|
62
|
+
Object.values(variantConfig).forEach((config) => {
|
|
63
|
+
expect(config.bgColor).toMatch(/dark:/);
|
|
64
|
+
expect(config.borderColor).toMatch(/dark:/);
|
|
65
|
+
expect(config.textColor).toMatch(/dark:/);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('Props', () => {
|
|
71
|
+
it('should accept optional title prop', () => {
|
|
72
|
+
const props = {
|
|
73
|
+
title: 'Important Note',
|
|
74
|
+
variant: 'info' as const
|
|
75
|
+
};
|
|
76
|
+
expect(props.title).toBeDefined();
|
|
77
|
+
expect(typeof props.title).toBe('string');
|
|
11
78
|
});
|
|
12
79
|
|
|
13
|
-
|
|
14
|
-
|
|
80
|
+
it('should accept children slot', () => {
|
|
81
|
+
const hasChildren = true;
|
|
82
|
+
expect(hasChildren).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should accept variant prop', () => {
|
|
86
|
+
const validVariants = ['info', 'warning', 'success', 'error'] as const;
|
|
87
|
+
const variant: typeof validVariants[number] = 'warning';
|
|
88
|
+
expect(validVariants).toContain(variant);
|
|
89
|
+
});
|
|
15
90
|
});
|
|
16
91
|
});
|