feat: storybook cases and mocks
This commit is contained in:
@@ -78,6 +78,56 @@ export {
|
||||
unifiedFontStore,
|
||||
} from './model';
|
||||
|
||||
// Mock data helpers for Storybook and testing
|
||||
export {
|
||||
createCategoriesFilter,
|
||||
createErrorState,
|
||||
createGenericFilter,
|
||||
createLoadingState,
|
||||
createMockComparisonStore,
|
||||
// Filter mocks
|
||||
createMockFilter,
|
||||
createMockFontApiResponse,
|
||||
createMockFontStoreState,
|
||||
// Store mocks
|
||||
createMockQueryState,
|
||||
createMockReactiveState,
|
||||
createMockStore,
|
||||
createProvidersFilter,
|
||||
createSubsetsFilter,
|
||||
createSuccessState,
|
||||
FONTHARE_FONTS,
|
||||
generateMixedCategoryFonts,
|
||||
generateMockFonts,
|
||||
generatePaginatedFonts,
|
||||
generateSequentialFilter,
|
||||
GENERIC_FILTERS,
|
||||
getAllMockFonts,
|
||||
getFontsByCategory,
|
||||
getFontsByProvider,
|
||||
GOOGLE_FONTS,
|
||||
MOCK_FILTERS,
|
||||
MOCK_FILTERS_ALL_SELECTED,
|
||||
MOCK_FILTERS_EMPTY,
|
||||
MOCK_FILTERS_SELECTED,
|
||||
MOCK_FONT_STORE_STATES,
|
||||
MOCK_STORES,
|
||||
type MockFilterOptions,
|
||||
type MockFilters,
|
||||
mockFontshareFont,
|
||||
type MockFontshareFontOptions,
|
||||
type MockFontStoreState,
|
||||
// Font mocks
|
||||
mockGoogleFont,
|
||||
// Types
|
||||
type MockGoogleFontOptions,
|
||||
type MockQueryObserverResult,
|
||||
type MockQueryState,
|
||||
mockUnifiedFont,
|
||||
type MockUnifiedFontOptions,
|
||||
UNIFIED_FONTS,
|
||||
} from './lib/mocks';
|
||||
|
||||
// UI elements
|
||||
export {
|
||||
FontApplicator,
|
||||
|
||||
@@ -6,3 +6,53 @@ export {
|
||||
} from './normalize/normalize';
|
||||
|
||||
export { getFontUrl } from './getFontUrl/getFontUrl';
|
||||
|
||||
// Mock data helpers for Storybook and testing
|
||||
export {
|
||||
createCategoriesFilter,
|
||||
createErrorState,
|
||||
createGenericFilter,
|
||||
createLoadingState,
|
||||
createMockComparisonStore,
|
||||
// Filter mocks
|
||||
createMockFilter,
|
||||
createMockFontApiResponse,
|
||||
createMockFontStoreState,
|
||||
// Store mocks
|
||||
createMockQueryState,
|
||||
createMockReactiveState,
|
||||
createMockStore,
|
||||
createProvidersFilter,
|
||||
createSubsetsFilter,
|
||||
createSuccessState,
|
||||
FONTHARE_FONTS,
|
||||
generateMixedCategoryFonts,
|
||||
generateMockFonts,
|
||||
generatePaginatedFonts,
|
||||
generateSequentialFilter,
|
||||
GENERIC_FILTERS,
|
||||
getAllMockFonts,
|
||||
getFontsByCategory,
|
||||
getFontsByProvider,
|
||||
GOOGLE_FONTS,
|
||||
MOCK_FILTERS,
|
||||
MOCK_FILTERS_ALL_SELECTED,
|
||||
MOCK_FILTERS_EMPTY,
|
||||
MOCK_FILTERS_SELECTED,
|
||||
MOCK_FONT_STORE_STATES,
|
||||
MOCK_STORES,
|
||||
type MockFilterOptions,
|
||||
type MockFilters,
|
||||
mockFontshareFont,
|
||||
type MockFontshareFontOptions,
|
||||
type MockFontStoreState,
|
||||
// Font mocks
|
||||
mockGoogleFont,
|
||||
// Types
|
||||
type MockGoogleFontOptions,
|
||||
type MockQueryObserverResult,
|
||||
type MockQueryState,
|
||||
mockUnifiedFont,
|
||||
type MockUnifiedFontOptions,
|
||||
UNIFIED_FONTS,
|
||||
} from './mocks';
|
||||
|
||||
348
src/entities/Font/lib/mocks/filters.mock.ts
Normal file
348
src/entities/Font/lib/mocks/filters.mock.ts
Normal file
@@ -0,0 +1,348 @@
|
||||
/**
|
||||
* ============================================================================
|
||||
* MOCK FONT FILTER DATA
|
||||
* ============================================================================
|
||||
*
|
||||
* Factory functions and preset mock data for font-related filters.
|
||||
* Used in Storybook stories for font filtering components.
|
||||
*
|
||||
* ## Usage
|
||||
*
|
||||
* ```ts
|
||||
* import {
|
||||
* createMockFilter,
|
||||
* MOCK_FILTERS,
|
||||
* } from '$entities/Font/lib/mocks';
|
||||
*
|
||||
* // Create a custom filter
|
||||
* const customFilter = createMockFilter({
|
||||
* properties: [
|
||||
* { id: 'option1', name: 'Option 1', value: 'option1' },
|
||||
* { id: 'option2', name: 'Option 2', value: 'option2', selected: true },
|
||||
* ],
|
||||
* });
|
||||
*
|
||||
* // Use preset filters
|
||||
* const categoriesFilter = MOCK_FILTERS.categories;
|
||||
* const subsetsFilter = MOCK_FILTERS.subsets;
|
||||
* ```
|
||||
*/
|
||||
|
||||
import type {
|
||||
FontCategory,
|
||||
FontProvider,
|
||||
FontSubset,
|
||||
} from '$entities/Font/model/types';
|
||||
import type { Property } from '$shared/lib';
|
||||
import { createFilter } from '$shared/lib';
|
||||
|
||||
// ============================================================================
|
||||
// TYPE DEFINITIONS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Options for creating a mock filter
|
||||
*/
|
||||
export interface MockFilterOptions {
|
||||
/** Filter properties */
|
||||
properties: Property<string>[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Preset mock filters for font filtering
|
||||
*/
|
||||
export interface MockFilters {
|
||||
/** Provider filter (Google, Fontshare) */
|
||||
providers: ReturnType<typeof createFilter<'google' | 'fontshare'>>;
|
||||
/** Category filter (sans-serif, serif, display, etc.) */
|
||||
categories: ReturnType<typeof createFilter<FontCategory>>;
|
||||
/** Subset filter (latin, latin-ext, cyrillic, etc.) */
|
||||
subsets: ReturnType<typeof createFilter<FontSubset>>;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// FONT CATEGORIES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Google Fonts categories
|
||||
*/
|
||||
export const GOOGLE_CATEGORIES: Property<'sans-serif' | 'serif' | 'display' | 'handwriting' | 'monospace'>[] = [
|
||||
{ id: 'sans-serif', name: 'Sans Serif', value: 'sans-serif' },
|
||||
{ id: 'serif', name: 'Serif', value: 'serif' },
|
||||
{ id: 'display', name: 'Display', value: 'display' },
|
||||
{ id: 'handwriting', name: 'Handwriting', value: 'handwriting' },
|
||||
{ id: 'monospace', name: 'Monospace', value: 'monospace' },
|
||||
];
|
||||
|
||||
/**
|
||||
* Fontshare categories (mapped to common naming)
|
||||
*/
|
||||
export const FONTHARE_CATEGORIES: Property<'sans' | 'serif' | 'slab' | 'display' | 'handwritten' | 'script'>[] = [
|
||||
{ id: 'sans', name: 'Sans', value: 'sans' },
|
||||
{ id: 'serif', name: 'Serif', value: 'serif' },
|
||||
{ id: 'slab', name: 'Slab', value: 'slab' },
|
||||
{ id: 'display', name: 'Display', value: 'display' },
|
||||
{ id: 'handwritten', name: 'Handwritten', value: 'handwritten' },
|
||||
{ id: 'script', name: 'Script', value: 'script' },
|
||||
];
|
||||
|
||||
/**
|
||||
* Unified categories (combines both providers)
|
||||
*/
|
||||
export const UNIFIED_CATEGORIES: Property<FontCategory>[] = [
|
||||
{ id: 'sans-serif', name: 'Sans Serif', value: 'sans-serif' },
|
||||
{ id: 'serif', name: 'Serif', value: 'serif' },
|
||||
{ id: 'display', name: 'Display', value: 'display' },
|
||||
{ id: 'handwriting', name: 'Handwriting', value: 'handwriting' },
|
||||
{ id: 'monospace', name: 'Monospace', value: 'monospace' },
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// FONT SUBSETS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Common font subsets
|
||||
*/
|
||||
export const FONT_SUBSETS: Property<FontSubset>[] = [
|
||||
{ id: 'latin', name: 'Latin', value: 'latin' },
|
||||
{ id: 'latin-ext', name: 'Latin Extended', value: 'latin-ext' },
|
||||
{ id: 'cyrillic', name: 'Cyrillic', value: 'cyrillic' },
|
||||
{ id: 'greek', name: 'Greek', value: 'greek' },
|
||||
{ id: 'arabic', name: 'Arabic', value: 'arabic' },
|
||||
{ id: 'devanagari', name: 'Devanagari', value: 'devanagari' },
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// FONT PROVIDERS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Font providers
|
||||
*/
|
||||
export const FONT_PROVIDERS: Property<FontProvider>[] = [
|
||||
{ id: 'google', name: 'Google Fonts', value: 'google' },
|
||||
{ id: 'fontshare', name: 'Fontshare', value: 'fontshare' },
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// FILTER FACTORIES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Create a mock filter from properties
|
||||
*/
|
||||
export function createMockFilter<TValue extends string>(
|
||||
options: MockFilterOptions & { properties: Property<TValue>[] },
|
||||
) {
|
||||
return createFilter<TValue>(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a mock filter for categories
|
||||
*/
|
||||
export function createCategoriesFilter(options?: { selected?: FontCategory[] }) {
|
||||
const properties = UNIFIED_CATEGORIES.map(cat => ({
|
||||
...cat,
|
||||
selected: options?.selected?.includes(cat.value) ?? false,
|
||||
}));
|
||||
return createFilter<FontCategory>({ properties });
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a mock filter for subsets
|
||||
*/
|
||||
export function createSubsetsFilter(options?: { selected?: FontSubset[] }) {
|
||||
const properties = FONT_SUBSETS.map(subset => ({
|
||||
...subset,
|
||||
selected: options?.selected?.includes(subset.value) ?? false,
|
||||
}));
|
||||
return createFilter<FontSubset>({ properties });
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a mock filter for providers
|
||||
*/
|
||||
export function createProvidersFilter(options?: { selected?: FontProvider[] }) {
|
||||
const properties = FONT_PROVIDERS.map(provider => ({
|
||||
...provider,
|
||||
selected: options?.selected?.includes(provider.value) ?? false,
|
||||
}));
|
||||
return createFilter<FontProvider>({ properties });
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// PRESET FILTERS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Preset mock filters - use these directly in stories
|
||||
*/
|
||||
export const MOCK_FILTERS: MockFilters = {
|
||||
providers: createFilter({
|
||||
properties: FONT_PROVIDERS,
|
||||
}),
|
||||
categories: createFilter({
|
||||
properties: UNIFIED_CATEGORIES,
|
||||
}),
|
||||
subsets: createFilter({
|
||||
properties: FONT_SUBSETS,
|
||||
}),
|
||||
};
|
||||
|
||||
/**
|
||||
* Preset filters with some items selected
|
||||
*/
|
||||
export const MOCK_FILTERS_SELECTED: MockFilters = {
|
||||
providers: createFilter({
|
||||
properties: [
|
||||
{ ...FONT_PROVIDERS[0], selected: true },
|
||||
{ ...FONT_PROVIDERS[1] },
|
||||
],
|
||||
}),
|
||||
categories: createFilter({
|
||||
properties: [
|
||||
{ ...UNIFIED_CATEGORIES[0], selected: true },
|
||||
{ ...UNIFIED_CATEGORIES[1], selected: true },
|
||||
{ ...UNIFIED_CATEGORIES[2] },
|
||||
{ ...UNIFIED_CATEGORIES[3] },
|
||||
{ ...UNIFIED_CATEGORIES[4] },
|
||||
],
|
||||
}),
|
||||
subsets: createFilter({
|
||||
properties: [
|
||||
{ ...FONT_SUBSETS[0], selected: true },
|
||||
{ ...FONT_SUBSETS[1] },
|
||||
{ ...FONT_SUBSETS[2] },
|
||||
{ ...FONT_SUBSETS[3] },
|
||||
{ ...FONT_SUBSETS[4] },
|
||||
],
|
||||
}),
|
||||
};
|
||||
|
||||
/**
|
||||
* Empty filters (all properties, none selected)
|
||||
*/
|
||||
export const MOCK_FILTERS_EMPTY: MockFilters = {
|
||||
providers: createFilter({
|
||||
properties: FONT_PROVIDERS.map(p => ({ ...p, selected: false })),
|
||||
}),
|
||||
categories: createFilter({
|
||||
properties: UNIFIED_CATEGORIES.map(c => ({ ...c, selected: false })),
|
||||
}),
|
||||
subsets: createFilter({
|
||||
properties: FONT_SUBSETS.map(s => ({ ...s, selected: false })),
|
||||
}),
|
||||
};
|
||||
|
||||
/**
|
||||
* All selected filters
|
||||
*/
|
||||
export const MOCK_FILTERS_ALL_SELECTED: MockFilters = {
|
||||
providers: createFilter({
|
||||
properties: FONT_PROVIDERS.map(p => ({ ...p, selected: true })),
|
||||
}),
|
||||
categories: createFilter({
|
||||
properties: UNIFIED_CATEGORIES.map(c => ({ ...c, selected: true })),
|
||||
}),
|
||||
subsets: createFilter({
|
||||
properties: FONT_SUBSETS.map(s => ({ ...s, selected: true })),
|
||||
}),
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// GENERIC FILTER MOCKS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Create a mock filter with generic string properties
|
||||
* Useful for testing generic filter components
|
||||
*/
|
||||
export function createGenericFilter(
|
||||
items: Array<{ id: string; name: string; selected?: boolean }>,
|
||||
options?: { selected?: string[] },
|
||||
) {
|
||||
const properties = items.map(item => ({
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
value: item.id,
|
||||
selected: options?.selected?.includes(item.id) ?? item.selected ?? false,
|
||||
}));
|
||||
return createFilter({ properties });
|
||||
}
|
||||
|
||||
/**
|
||||
* Preset generic filters for testing
|
||||
*/
|
||||
export const GENERIC_FILTERS = {
|
||||
/** Small filter with 3 items */
|
||||
small: createFilter({
|
||||
properties: [
|
||||
{ id: 'option-1', name: 'Option 1', value: 'option-1' },
|
||||
{ id: 'option-2', name: 'Option 2', value: 'option-2' },
|
||||
{ id: 'option-3', name: 'Option 3', value: 'option-3' },
|
||||
],
|
||||
}),
|
||||
/** Medium filter with 6 items */
|
||||
medium: createFilter({
|
||||
properties: [
|
||||
{ id: 'alpha', name: 'Alpha', value: 'alpha' },
|
||||
{ id: 'beta', name: 'Beta', value: 'beta' },
|
||||
{ id: 'gamma', name: 'Gamma', value: 'gamma' },
|
||||
{ id: 'delta', name: 'Delta', value: 'delta' },
|
||||
{ id: 'epsilon', name: 'Epsilon', value: 'epsilon' },
|
||||
{ id: 'zeta', name: 'Zeta', value: 'zeta' },
|
||||
],
|
||||
}),
|
||||
/** Large filter with 12 items */
|
||||
large: createFilter({
|
||||
properties: [
|
||||
{ id: 'jan', name: 'January', value: 'jan' },
|
||||
{ id: 'feb', name: 'February', value: 'feb' },
|
||||
{ id: 'mar', name: 'March', value: 'mar' },
|
||||
{ id: 'apr', name: 'April', value: 'apr' },
|
||||
{ id: 'may', name: 'May', value: 'may' },
|
||||
{ id: 'jun', name: 'June', value: 'jun' },
|
||||
{ id: 'jul', name: 'July', value: 'jul' },
|
||||
{ id: 'aug', name: 'August', value: 'aug' },
|
||||
{ id: 'sep', name: 'September', value: 'sep' },
|
||||
{ id: 'oct', name: 'October', value: 'oct' },
|
||||
{ id: 'nov', name: 'November', value: 'nov' },
|
||||
{ id: 'dec', name: 'December', value: 'dec' },
|
||||
],
|
||||
}),
|
||||
/** Filter with some pre-selected items */
|
||||
partial: createFilter({
|
||||
properties: [
|
||||
{ id: 'red', name: 'Red', value: 'red', selected: true },
|
||||
{ id: 'blue', name: 'Blue', value: 'blue', selected: false },
|
||||
{ id: 'green', name: 'Green', value: 'green', selected: true },
|
||||
{ id: 'yellow', name: 'Yellow', value: 'yellow', selected: false },
|
||||
],
|
||||
}),
|
||||
/** Filter with all items selected */
|
||||
allSelected: createFilter({
|
||||
properties: [
|
||||
{ id: 'cat', name: 'Cat', value: 'cat', selected: true },
|
||||
{ id: 'dog', name: 'Dog', value: 'dog', selected: true },
|
||||
{ id: 'bird', name: 'Bird', value: 'bird', selected: true },
|
||||
],
|
||||
}),
|
||||
/** Empty filter (no items) */
|
||||
empty: createFilter({
|
||||
properties: [],
|
||||
}),
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate a filter with sequential items
|
||||
*/
|
||||
export function generateSequentialFilter(count: number, prefix = 'Item ') {
|
||||
const properties = Array.from({ length: count }, (_, i) => ({
|
||||
id: `item-${i + 1}`,
|
||||
name: `${prefix}${i + 1}`,
|
||||
value: `item-${i + 1}`,
|
||||
}));
|
||||
return createFilter({ properties });
|
||||
}
|
||||
630
src/entities/Font/lib/mocks/fonts.mock.ts
Normal file
630
src/entities/Font/lib/mocks/fonts.mock.ts
Normal file
@@ -0,0 +1,630 @@
|
||||
/**
|
||||
* ============================================================================
|
||||
* MOCK FONT DATA
|
||||
* ============================================================================
|
||||
*
|
||||
* Factory functions and preset mock data for fonts.
|
||||
* Used in Storybook stories, tests, and development.
|
||||
*
|
||||
* ## Usage
|
||||
*
|
||||
* ```ts
|
||||
* import {
|
||||
* mockGoogleFont,
|
||||
* mockFontshareFont,
|
||||
* mockUnifiedFont,
|
||||
* GOOGLE_FONTS,
|
||||
* FONTHARE_FONTS,
|
||||
* UNIFIED_FONTS,
|
||||
* } from '$entities/Font/lib/mocks';
|
||||
*
|
||||
* // Create a mock Google Font
|
||||
* const roboto = mockGoogleFont({ family: 'Roboto', category: 'sans-serif' });
|
||||
*
|
||||
* // Create a mock Fontshare font
|
||||
* const satoshi = mockFontshareFont({ name: 'Satoshi', slug: 'satoshi' });
|
||||
*
|
||||
* // Create a mock UnifiedFont
|
||||
* const font = mockUnifiedFont({ id: 'roboto', name: 'Roboto' });
|
||||
*
|
||||
* // Use preset fonts
|
||||
* import { UNIFIED_FONTS } from '$entities/Font/lib/mocks';
|
||||
* ```
|
||||
*/
|
||||
|
||||
import type {
|
||||
FontCategory,
|
||||
FontProvider,
|
||||
FontSubset,
|
||||
FontVariant,
|
||||
} from '$entities/Font/model/types';
|
||||
import type {
|
||||
FontItem,
|
||||
FontshareFont,
|
||||
GoogleFontItem,
|
||||
} from '$entities/Font/model/types';
|
||||
import type {
|
||||
FontFeatures,
|
||||
FontMetadata,
|
||||
FontStyleUrls,
|
||||
UnifiedFont,
|
||||
} from '$entities/Font/model/types';
|
||||
|
||||
// ============================================================================
|
||||
// GOOGLE FONTS MOCKS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Options for creating a mock Google Font
|
||||
*/
|
||||
export interface MockGoogleFontOptions {
|
||||
/** Font family name (default: 'Mock Font') */
|
||||
family?: string;
|
||||
/** Font category (default: 'sans-serif') */
|
||||
category?: 'sans-serif' | 'serif' | 'display' | 'handwriting' | 'monospace';
|
||||
/** Font variants (default: ['regular', '700', 'italic', '700italic']) */
|
||||
variants?: FontVariant[];
|
||||
/** Font subsets (default: ['latin']) */
|
||||
subsets?: string[];
|
||||
/** Font version (default: 'v30') */
|
||||
version?: string;
|
||||
/** Last modified date (default: current ISO date) */
|
||||
lastModified?: string;
|
||||
/** Custom file URLs (if not provided, mock URLs are generated) */
|
||||
files?: Partial<Record<FontVariant, string>>;
|
||||
/** Popularity rank (1 = most popular) */
|
||||
popularity?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default mock Google Font
|
||||
*/
|
||||
export function mockGoogleFont(options: MockGoogleFontOptions = {}): GoogleFontItem {
|
||||
const {
|
||||
family = 'Mock Font',
|
||||
category = 'sans-serif',
|
||||
variants = ['regular', '700', 'italic', '700italic'],
|
||||
subsets = ['latin'],
|
||||
version = 'v30',
|
||||
lastModified = new Date().toISOString().split('T')[0],
|
||||
files,
|
||||
popularity = 1,
|
||||
} = options;
|
||||
|
||||
const baseUrl = `https://fonts.gstatic.com/s/${family.toLowerCase().replace(/\s+/g, '')}/${version}`;
|
||||
|
||||
return {
|
||||
family,
|
||||
category,
|
||||
variants: variants as FontVariant[],
|
||||
subsets,
|
||||
version,
|
||||
lastModified,
|
||||
files: files ?? {
|
||||
regular: `${baseUrl}/KFOmCnqEu92Fr1Me4W.woff2`,
|
||||
'700': `${baseUrl}/KFOlCnqEu92Fr1MmWUlfBBc9.woff2`,
|
||||
italic: `${baseUrl}/KFOkCnqEu92Fr1Mu51xIIzI.woff2`,
|
||||
'700italic': `${baseUrl}/KFOjCnqEu92Fr1Mu51TzBic6CsQ.woff2`,
|
||||
},
|
||||
menu: `https://fonts.googleapis.com/css2?family=${encodeURIComponent(family)}`,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Preset Google Font mocks
|
||||
*/
|
||||
export const GOOGLE_FONTS: Record<string, GoogleFontItem> = {
|
||||
roboto: mockGoogleFont({
|
||||
family: 'Roboto',
|
||||
category: 'sans-serif',
|
||||
variants: ['100', '300', '400', '500', '700', '900', 'italic', '700italic'],
|
||||
subsets: ['latin', 'latin-ext', 'cyrillic', 'greek'],
|
||||
popularity: 1,
|
||||
}),
|
||||
openSans: mockGoogleFont({
|
||||
family: 'Open Sans',
|
||||
category: 'sans-serif',
|
||||
variants: ['300', '400', '500', '600', '700', '800', 'italic', '700italic'],
|
||||
subsets: ['latin', 'latin-ext', 'cyrillic', 'greek'],
|
||||
popularity: 2,
|
||||
}),
|
||||
lato: mockGoogleFont({
|
||||
family: 'Lato',
|
||||
category: 'sans-serif',
|
||||
variants: ['100', '300', '400', '700', '900', 'italic', '700italic'],
|
||||
subsets: ['latin', 'latin-ext'],
|
||||
popularity: 3,
|
||||
}),
|
||||
playfairDisplay: mockGoogleFont({
|
||||
family: 'Playfair Display',
|
||||
category: 'serif',
|
||||
variants: ['400', '500', '600', '700', '800', '900', 'italic', '700italic'],
|
||||
subsets: ['latin', 'latin-ext', 'cyrillic'],
|
||||
popularity: 10,
|
||||
}),
|
||||
montserrat: mockGoogleFont({
|
||||
family: 'Montserrat',
|
||||
category: 'sans-serif',
|
||||
variants: ['100', '200', '300', '400', '500', '600', '700', '800', '900', 'italic', '700italic'],
|
||||
subsets: ['latin', 'latin-ext', 'cyrillic', 'vietnamese'],
|
||||
popularity: 4,
|
||||
}),
|
||||
sourceSansPro: mockGoogleFont({
|
||||
family: 'Source Sans Pro',
|
||||
category: 'sans-serif',
|
||||
variants: ['200', '300', '400', '600', '700', '900', 'italic', '700italic'],
|
||||
subsets: ['latin', 'latin-ext', 'cyrillic', 'greek', 'vietnamese'],
|
||||
popularity: 5,
|
||||
}),
|
||||
merriweather: mockGoogleFont({
|
||||
family: 'Merriweather',
|
||||
category: 'serif',
|
||||
variants: ['300', '400', '700', '900', 'italic', '700italic'],
|
||||
subsets: ['latin', 'latin-ext', 'cyrillic', 'vietnamese'],
|
||||
popularity: 15,
|
||||
}),
|
||||
robotoSlab: mockGoogleFont({
|
||||
family: 'Roboto Slab',
|
||||
category: 'serif',
|
||||
variants: ['100', '300', '400', '500', '700', '900'],
|
||||
subsets: ['latin', 'latin-ext', 'cyrillic', 'greek', 'vietnamese'],
|
||||
popularity: 8,
|
||||
}),
|
||||
oswald: mockGoogleFont({
|
||||
family: 'Oswald',
|
||||
category: 'sans-serif',
|
||||
variants: ['200', '300', '400', '500', '600', '700'],
|
||||
subsets: ['latin', 'latin-ext', 'vietnamese'],
|
||||
popularity: 6,
|
||||
}),
|
||||
raleway: mockGoogleFont({
|
||||
family: 'Raleway',
|
||||
category: 'sans-serif',
|
||||
variants: ['100', '200', '300', '400', '500', '600', '700', '800', '900', 'italic'],
|
||||
subsets: ['latin', 'latin-ext', 'cyrillic', 'vietnamese'],
|
||||
popularity: 7,
|
||||
}),
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// FONTHARE MOCKS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Options for creating a mock Fontshare font
|
||||
*/
|
||||
export interface MockFontshareFontOptions {
|
||||
/** Font name (default: 'Mock Font') */
|
||||
name?: string;
|
||||
/** URL-friendly slug (default: derived from name) */
|
||||
slug?: string;
|
||||
/** Font category (default: 'sans') */
|
||||
category?: 'sans' | 'serif' | 'slab' | 'display' | 'handwritten' | 'script' | 'mono';
|
||||
/** Script (default: 'latin') */
|
||||
script?: string;
|
||||
/** Whether this is a variable font (default: false) */
|
||||
isVariable?: boolean;
|
||||
/** Font version (default: '1.0') */
|
||||
version?: string;
|
||||
/** Popularity/views count (default: 1000) */
|
||||
views?: number;
|
||||
/** Usage tags */
|
||||
tags?: string[];
|
||||
/** Font weights available */
|
||||
weights?: number[];
|
||||
/** Publisher name */
|
||||
publisher?: string;
|
||||
/** Designer name */
|
||||
designer?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a mock Fontshare style
|
||||
*/
|
||||
function mockFontshareStyle(
|
||||
weight: number,
|
||||
isItalic: boolean,
|
||||
isVariable: boolean,
|
||||
slug: string,
|
||||
): FontshareFont['styles'][number] {
|
||||
const weightLabel = weight === 400 ? 'Regular' : weight === 700 ? 'Bold' : weight.toString();
|
||||
const suffix = isItalic ? 'italic' : '';
|
||||
const variablePrefix = isVariable ? 'variable-' : '';
|
||||
|
||||
return {
|
||||
id: `style-${weight}${isItalic ? '-italic' : ''}`,
|
||||
default: weight === 400 && !isItalic,
|
||||
file: `//cdn.fontshare.com/wf/${slug}-${variablePrefix}${weight}${suffix}.woff2`,
|
||||
is_italic: isItalic,
|
||||
is_variable: isVariable,
|
||||
properties: {},
|
||||
weight: {
|
||||
label: isVariable ? 'Variable' + (isItalic ? ' Italic' : '') : weightLabel,
|
||||
name: isVariable ? 'Variable' + (isItalic ? 'Italic' : '') : weightLabel,
|
||||
native_name: null,
|
||||
number: isVariable ? 0 : weight,
|
||||
weight: isVariable ? 0 : weight,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Default mock Fontshare font
|
||||
*/
|
||||
export function mockFontshareFont(options: MockFontshareFontOptions = {}): FontshareFont {
|
||||
const {
|
||||
name = 'Mock Font',
|
||||
slug = name.toLowerCase().replace(/\s+/g, '-'),
|
||||
category = 'sans',
|
||||
script = 'latin',
|
||||
isVariable = false,
|
||||
version = '1.0',
|
||||
views = 1000,
|
||||
tags = [],
|
||||
weights = [400, 700],
|
||||
publisher = 'Mock Foundry',
|
||||
designer = 'Mock Designer',
|
||||
} = options;
|
||||
|
||||
// Generate styles based on weights and variable setting
|
||||
const styles: FontshareFont['styles'] = isVariable
|
||||
? [
|
||||
mockFontshareStyle(0, false, true, slug),
|
||||
mockFontshareStyle(0, true, true, slug),
|
||||
]
|
||||
: weights.flatMap(weight => [
|
||||
mockFontshareStyle(weight, false, false, slug),
|
||||
mockFontshareStyle(weight, true, false, slug),
|
||||
]);
|
||||
|
||||
return {
|
||||
id: `mock-${slug}`,
|
||||
name,
|
||||
native_name: null,
|
||||
slug,
|
||||
category,
|
||||
script,
|
||||
publisher: {
|
||||
bio: `Mock publisher bio for ${publisher}`,
|
||||
email: null,
|
||||
id: `pub-${slug}`,
|
||||
links: [],
|
||||
name: publisher,
|
||||
},
|
||||
designers: [
|
||||
{
|
||||
bio: `Mock designer bio for ${designer}`,
|
||||
links: [],
|
||||
name: designer,
|
||||
},
|
||||
],
|
||||
related_families: null,
|
||||
display_publisher_as_designer: false,
|
||||
trials_enabled: true,
|
||||
show_latin_metrics: false,
|
||||
license_type: 'ofl',
|
||||
languages: 'English, Spanish, French, German',
|
||||
inserted_at: '2021-03-12T20:49:05Z',
|
||||
story: `<p>A mock font story for ${name}.</p>`,
|
||||
version,
|
||||
views,
|
||||
views_recent: Math.floor(views * 0.1),
|
||||
is_hot: views > 5000,
|
||||
is_new: views < 500,
|
||||
is_shortlisted: null,
|
||||
is_top: views > 10000,
|
||||
axes: isVariable
|
||||
? [
|
||||
{
|
||||
name: 'Weight',
|
||||
property: 'wght',
|
||||
range_default: 400,
|
||||
range_left: 300,
|
||||
range_right: 700,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
font_tags: tags.map(name => ({ name })),
|
||||
features: [],
|
||||
styles,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Preset Fontshare font mocks
|
||||
*/
|
||||
export const FONTHARE_FONTS: Record<string, FontshareFont> = {
|
||||
satoshi: mockFontshareFont({
|
||||
name: 'Satoshi',
|
||||
slug: 'satoshi',
|
||||
category: 'sans',
|
||||
isVariable: true,
|
||||
views: 15000,
|
||||
tags: ['Branding', 'Logos', 'Editorial'],
|
||||
publisher: 'Indian Type Foundry',
|
||||
designer: 'Denis Shelabovets',
|
||||
}),
|
||||
generalSans: mockFontshareFont({
|
||||
name: 'General Sans',
|
||||
slug: 'general-sans',
|
||||
category: 'sans',
|
||||
isVariable: true,
|
||||
views: 12000,
|
||||
tags: ['UI', 'Branding', 'Display'],
|
||||
publisher: 'Indestructible Type',
|
||||
designer: 'Eugene Tantsur',
|
||||
}),
|
||||
clashDisplay: mockFontshareFont({
|
||||
name: 'Clash Display',
|
||||
slug: 'clash-display',
|
||||
category: 'display',
|
||||
isVariable: false,
|
||||
views: 8000,
|
||||
tags: ['Headlines', 'Posters', 'Branding'],
|
||||
weights: [400, 500, 600, 700],
|
||||
publisher: 'Letterogika',
|
||||
designer: 'Matěj Trnka',
|
||||
}),
|
||||
fonta: mockFontshareFont({
|
||||
name: 'Fonta',
|
||||
slug: 'fonta',
|
||||
category: 'serif',
|
||||
isVariable: false,
|
||||
views: 5000,
|
||||
tags: ['Editorial', 'Books', 'Magazines'],
|
||||
weights: [300, 400, 500, 600, 700],
|
||||
publisher: 'Fonta',
|
||||
designer: 'Alexei Vanyashin',
|
||||
}),
|
||||
aileron: mockFontshareFont({
|
||||
name: 'Aileron',
|
||||
slug: 'aileron',
|
||||
category: 'sans',
|
||||
isVariable: false,
|
||||
views: 3000,
|
||||
tags: ['Display', 'Headlines'],
|
||||
weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],
|
||||
publisher: 'Sorkin Type',
|
||||
designer: 'Sorkin Type',
|
||||
}),
|
||||
beVietnamPro: mockFontshareFont({
|
||||
name: 'Be Vietnam Pro',
|
||||
slug: 'be-vietnam-pro',
|
||||
category: 'sans',
|
||||
isVariable: true,
|
||||
views: 20000,
|
||||
tags: ['UI', 'App', 'Web'],
|
||||
publisher: 'ildefox',
|
||||
designer: 'Manh Nguyen',
|
||||
}),
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// UNIFIED FONT MOCKS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Options for creating a mock UnifiedFont
|
||||
*/
|
||||
export interface MockUnifiedFontOptions {
|
||||
/** Unique identifier (default: derived from name) */
|
||||
id?: string;
|
||||
/** Font display name (default: 'Mock Font') */
|
||||
name?: string;
|
||||
/** Font provider (default: 'google') */
|
||||
provider?: FontProvider;
|
||||
/** Font category (default: 'sans-serif') */
|
||||
category?: FontCategory;
|
||||
/** Font subsets (default: ['latin']) */
|
||||
subsets?: FontSubset[];
|
||||
/** Font variants (default: ['regular', '700', 'italic', '700italic']) */
|
||||
variants?: FontVariant[];
|
||||
/** Style URLs (if not provided, mock URLs are generated) */
|
||||
styles?: FontStyleUrls;
|
||||
/** Metadata overrides */
|
||||
metadata?: Partial<FontMetadata>;
|
||||
/** Features overrides */
|
||||
features?: Partial<FontFeatures>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default mock UnifiedFont
|
||||
*/
|
||||
export function mockUnifiedFont(options: MockUnifiedFontOptions = {}): UnifiedFont {
|
||||
const {
|
||||
id,
|
||||
name = 'Mock Font',
|
||||
provider = 'google',
|
||||
category = 'sans-serif',
|
||||
subsets = ['latin'],
|
||||
variants = ['regular', '700', 'italic', '700italic'],
|
||||
styles,
|
||||
metadata,
|
||||
features,
|
||||
} = options;
|
||||
|
||||
const fontId = id ?? name.toLowerCase().replace(/\s+/g, '');
|
||||
const baseUrl = provider === 'google'
|
||||
? `https://fonts.gstatic.com/s/${fontId}/v30`
|
||||
: `//cdn.fontshare.com/wf/${fontId}`;
|
||||
|
||||
return {
|
||||
id: fontId,
|
||||
name,
|
||||
provider,
|
||||
category,
|
||||
subsets,
|
||||
variants: variants as FontVariant[],
|
||||
styles: styles ?? {
|
||||
regular: `${baseUrl}/regular.woff2`,
|
||||
bold: `${baseUrl}/bold.woff2`,
|
||||
italic: `${baseUrl}/italic.woff2`,
|
||||
boldItalic: `${baseUrl}/bolditalic.woff2`,
|
||||
},
|
||||
metadata: {
|
||||
cachedAt: Date.now(),
|
||||
version: '1.0',
|
||||
lastModified: new Date().toISOString().split('T')[0],
|
||||
popularity: 1,
|
||||
...metadata,
|
||||
},
|
||||
features: {
|
||||
isVariable: false,
|
||||
...features,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Preset UnifiedFont mocks
|
||||
*/
|
||||
export const UNIFIED_FONTS: Record<string, UnifiedFont> = {
|
||||
roboto: mockUnifiedFont({
|
||||
id: 'roboto',
|
||||
name: 'Roboto',
|
||||
provider: 'google',
|
||||
category: 'sans-serif',
|
||||
subsets: ['latin', 'latin-ext'],
|
||||
variants: ['100', '300', '400', '500', '700', '900'],
|
||||
metadata: { popularity: 1 },
|
||||
}),
|
||||
openSans: mockUnifiedFont({
|
||||
id: 'open-sans',
|
||||
name: 'Open Sans',
|
||||
provider: 'google',
|
||||
category: 'sans-serif',
|
||||
subsets: ['latin', 'latin-ext'],
|
||||
variants: ['300', '400', '500', '600', '700', '800'],
|
||||
metadata: { popularity: 2 },
|
||||
}),
|
||||
lato: mockUnifiedFont({
|
||||
id: 'lato',
|
||||
name: 'Lato',
|
||||
provider: 'google',
|
||||
category: 'sans-serif',
|
||||
subsets: ['latin', 'latin-ext'],
|
||||
variants: ['100', '300', '400', '700', '900'],
|
||||
metadata: { popularity: 3 },
|
||||
}),
|
||||
playfairDisplay: mockUnifiedFont({
|
||||
id: 'playfair-display',
|
||||
name: 'Playfair Display',
|
||||
provider: 'google',
|
||||
category: 'serif',
|
||||
subsets: ['latin'],
|
||||
variants: ['400', '700', '900'],
|
||||
metadata: { popularity: 10 },
|
||||
}),
|
||||
montserrat: mockUnifiedFont({
|
||||
id: 'montserrat',
|
||||
name: 'Montserrat',
|
||||
provider: 'google',
|
||||
category: 'sans-serif',
|
||||
subsets: ['latin', 'latin-ext'],
|
||||
variants: ['100', '200', '300', '400', '500', '600', '700', '800', '900'],
|
||||
metadata: { popularity: 4 },
|
||||
}),
|
||||
satoshi: mockUnifiedFont({
|
||||
id: 'satoshi',
|
||||
name: 'Satoshi',
|
||||
provider: 'fontshare',
|
||||
category: 'sans-serif',
|
||||
subsets: ['latin'],
|
||||
variants: ['regular', 'bold', 'italic', 'bolditalic'] as FontVariant[],
|
||||
features: { isVariable: true, axes: [{ name: 'wght', property: 'wght', default: 400, min: 300, max: 700 }] },
|
||||
metadata: { popularity: 15000 },
|
||||
}),
|
||||
generalSans: mockUnifiedFont({
|
||||
id: 'general-sans',
|
||||
name: 'General Sans',
|
||||
provider: 'fontshare',
|
||||
category: 'sans-serif',
|
||||
subsets: ['latin'],
|
||||
variants: ['regular', 'bold', 'italic', 'bolditalic'] as FontVariant[],
|
||||
features: { isVariable: true },
|
||||
metadata: { popularity: 12000 },
|
||||
}),
|
||||
clashDisplay: mockUnifiedFont({
|
||||
id: 'clash-display',
|
||||
name: 'Clash Display',
|
||||
provider: 'fontshare',
|
||||
category: 'display',
|
||||
subsets: ['latin'],
|
||||
variants: ['regular', '500', '600', 'bold'] as FontVariant[],
|
||||
features: { tags: ['Headlines', 'Posters', 'Branding'] },
|
||||
metadata: { popularity: 8000 },
|
||||
}),
|
||||
oswald: mockUnifiedFont({
|
||||
id: 'oswald',
|
||||
name: 'Oswald',
|
||||
provider: 'google',
|
||||
category: 'sans-serif',
|
||||
subsets: ['latin'],
|
||||
variants: ['200', '300', '400', '500', '600', '700'],
|
||||
metadata: { popularity: 6 },
|
||||
}),
|
||||
raleway: mockUnifiedFont({
|
||||
id: 'raleway',
|
||||
name: 'Raleway',
|
||||
provider: 'google',
|
||||
category: 'sans-serif',
|
||||
subsets: ['latin'],
|
||||
variants: ['100', '200', '300', '400', '500', '600', '700', '800', '900'],
|
||||
metadata: { popularity: 7 },
|
||||
}),
|
||||
};
|
||||
|
||||
/**
|
||||
* Get an array of all preset UnifiedFonts
|
||||
*/
|
||||
export function getAllMockFonts(): UnifiedFont[] {
|
||||
return Object.values(UNIFIED_FONTS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get fonts by provider
|
||||
*/
|
||||
export function getFontsByProvider(provider: FontProvider): UnifiedFont[] {
|
||||
return getAllMockFonts().filter(font => font.provider === provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get fonts by category
|
||||
*/
|
||||
export function getFontsByCategory(category: FontCategory): UnifiedFont[] {
|
||||
return getAllMockFonts().filter(font => font.category === category);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an array of mock fonts with sequential naming
|
||||
*/
|
||||
export function generateMockFonts(count: number, options?: Omit<MockUnifiedFontOptions, 'id' | 'name'>): UnifiedFont[] {
|
||||
return Array.from({ length: count }, (_, i) =>
|
||||
mockUnifiedFont({
|
||||
...options,
|
||||
id: `mock-font-${i + 1}`,
|
||||
name: `Mock Font ${i + 1}`,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an array of mock fonts with different categories
|
||||
*/
|
||||
export function generateMixedCategoryFonts(countPerCategory: number = 2): UnifiedFont[] {
|
||||
const categories: FontCategory[] = ['sans-serif', 'serif', 'display', 'handwriting', 'monospace'];
|
||||
const fonts: UnifiedFont[] = [];
|
||||
|
||||
categories.forEach(category => {
|
||||
for (let i = 0; i < countPerCategory; i++) {
|
||||
fonts.push(
|
||||
mockUnifiedFont({
|
||||
id: `${category}-${i + 1}`,
|
||||
name: `${category.replace('-', ' ')} ${i + 1}`,
|
||||
category,
|
||||
}),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return fonts;
|
||||
}
|
||||
84
src/entities/Font/lib/mocks/index.ts
Normal file
84
src/entities/Font/lib/mocks/index.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* ============================================================================
|
||||
* MOCK DATA HELPERS - MAIN EXPORT
|
||||
* ============================================================================
|
||||
*
|
||||
* Comprehensive mock data for Storybook stories, tests, and development.
|
||||
*
|
||||
* ## Quick Start
|
||||
*
|
||||
* ```ts
|
||||
* import {
|
||||
* mockUnifiedFont,
|
||||
* UNIFIED_FONTS,
|
||||
* MOCK_FILTERS,
|
||||
* createMockFontStoreState,
|
||||
* } from '$entities/Font/lib/mocks';
|
||||
*
|
||||
* // Use in stories
|
||||
* const font = mockUnifiedFont({ name: 'My Font', category: 'serif' });
|
||||
* const presets = UNIFIED_FONTS;
|
||||
* const filter = MOCK_FILTERS.categories;
|
||||
* ```
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
// Font mocks
|
||||
export {
|
||||
FONTHARE_FONTS,
|
||||
generateMixedCategoryFonts,
|
||||
generateMockFonts,
|
||||
getAllMockFonts,
|
||||
getFontsByCategory,
|
||||
getFontsByProvider,
|
||||
GOOGLE_FONTS,
|
||||
mockFontshareFont,
|
||||
type MockFontshareFontOptions,
|
||||
mockGoogleFont,
|
||||
type MockGoogleFontOptions,
|
||||
mockUnifiedFont,
|
||||
type MockUnifiedFontOptions,
|
||||
UNIFIED_FONTS,
|
||||
} from './fonts.mock';
|
||||
|
||||
// Filter mocks
|
||||
export {
|
||||
createCategoriesFilter,
|
||||
createGenericFilter,
|
||||
createMockFilter,
|
||||
createProvidersFilter,
|
||||
createSubsetsFilter,
|
||||
FONT_PROVIDERS,
|
||||
FONT_SUBSETS,
|
||||
FONTHARE_CATEGORIES,
|
||||
generateSequentialFilter,
|
||||
GENERIC_FILTERS,
|
||||
GOOGLE_CATEGORIES,
|
||||
MOCK_FILTERS,
|
||||
MOCK_FILTERS_ALL_SELECTED,
|
||||
MOCK_FILTERS_EMPTY,
|
||||
MOCK_FILTERS_SELECTED,
|
||||
type MockFilterOptions,
|
||||
type MockFilters,
|
||||
UNIFIED_CATEGORIES,
|
||||
} from './filters.mock';
|
||||
|
||||
// Store mocks
|
||||
export {
|
||||
createErrorState,
|
||||
createLoadingState,
|
||||
createMockComparisonStore,
|
||||
createMockFontApiResponse,
|
||||
createMockFontStoreState,
|
||||
createMockQueryState,
|
||||
createMockReactiveState,
|
||||
createMockStore,
|
||||
createSuccessState,
|
||||
generatePaginatedFonts,
|
||||
MOCK_FONT_STORE_STATES,
|
||||
MOCK_STORES,
|
||||
type MockFontStoreState,
|
||||
type MockQueryObserverResult,
|
||||
type MockQueryState,
|
||||
} from './stores.mock';
|
||||
590
src/entities/Font/lib/mocks/stores.mock.ts
Normal file
590
src/entities/Font/lib/mocks/stores.mock.ts
Normal file
@@ -0,0 +1,590 @@
|
||||
/**
|
||||
* ============================================================================
|
||||
* MOCK FONT STORE HELPERS
|
||||
* ============================================================================
|
||||
*
|
||||
* Factory functions and preset mock data for TanStack Query stores and state management.
|
||||
* Used in Storybook stories for components that use reactive stores.
|
||||
*
|
||||
* ## Usage
|
||||
*
|
||||
* ```ts
|
||||
* import {
|
||||
* createMockQueryState,
|
||||
* MOCK_STORES,
|
||||
* } from '$entities/Font/lib/mocks';
|
||||
*
|
||||
* // Create a mock query state
|
||||
* const loadingState = createMockQueryState({ status: 'pending' });
|
||||
* const errorState = createMockQueryState({ status: 'error', error: 'Failed to load' });
|
||||
* const successState = createMockQueryState({ status: 'success', data: mockFonts });
|
||||
*
|
||||
* // Use preset stores
|
||||
* const mockFontStore = MOCK_STORES.unifiedFontStore();
|
||||
* ```
|
||||
*/
|
||||
|
||||
import type { UnifiedFont } from '$entities/Font/model/types';
|
||||
import type {
|
||||
QueryKey,
|
||||
QueryObserverResult,
|
||||
QueryStatus,
|
||||
} from '@tanstack/svelte-query';
|
||||
import {
|
||||
UNIFIED_FONTS,
|
||||
generateMockFonts,
|
||||
} from './fonts.mock';
|
||||
|
||||
// ============================================================================
|
||||
// TANSTACK QUERY MOCK TYPES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Mock TanStack Query state
|
||||
*/
|
||||
export interface MockQueryState<TData = unknown, TError = Error> {
|
||||
status: QueryStatus;
|
||||
data?: TData;
|
||||
error?: TError;
|
||||
isLoading?: boolean;
|
||||
isFetching?: boolean;
|
||||
isSuccess?: boolean;
|
||||
isError?: boolean;
|
||||
isPending?: boolean;
|
||||
dataUpdatedAt?: number;
|
||||
errorUpdatedAt?: number;
|
||||
failureCount?: number;
|
||||
failureReason?: TError;
|
||||
errorUpdateCount?: number;
|
||||
isRefetching?: boolean;
|
||||
isRefetchError?: boolean;
|
||||
isPaused?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock TanStack Query observer result
|
||||
*/
|
||||
export interface MockQueryObserverResult<TData = unknown, TError = Error> {
|
||||
status?: QueryStatus;
|
||||
data?: TData;
|
||||
error?: TError;
|
||||
isLoading?: boolean;
|
||||
isFetching?: boolean;
|
||||
isSuccess?: boolean;
|
||||
isError?: boolean;
|
||||
isPending?: boolean;
|
||||
dataUpdatedAt?: number;
|
||||
errorUpdatedAt?: number;
|
||||
failureCount?: number;
|
||||
failureReason?: TError;
|
||||
errorUpdateCount?: number;
|
||||
isRefetching?: boolean;
|
||||
isRefetchError?: boolean;
|
||||
isPaused?: boolean;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TANSTACK QUERY MOCK FACTORIES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Create a mock query state for TanStack Query
|
||||
*/
|
||||
export function createMockQueryState<TData = unknown, TError = Error>(
|
||||
options: MockQueryState<TData, TError>,
|
||||
): MockQueryObserverResult<TData, TError> {
|
||||
const {
|
||||
status,
|
||||
data,
|
||||
error,
|
||||
} = options;
|
||||
|
||||
return {
|
||||
status: status ?? 'success',
|
||||
data,
|
||||
error,
|
||||
isLoading: status === 'pending' ? true : false,
|
||||
isFetching: status === 'pending' ? true : false,
|
||||
isSuccess: status === 'success',
|
||||
isError: status === 'error',
|
||||
isPending: status === 'pending',
|
||||
dataUpdatedAt: status === 'success' ? Date.now() : undefined,
|
||||
errorUpdatedAt: status === 'error' ? Date.now() : undefined,
|
||||
failureCount: status === 'error' ? 1 : 0,
|
||||
failureReason: status === 'error' ? error : undefined,
|
||||
errorUpdateCount: status === 'error' ? 1 : 0,
|
||||
isRefetching: false,
|
||||
isRefetchError: false,
|
||||
isPaused: false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a loading query state
|
||||
*/
|
||||
export function createLoadingState<TData = unknown>(): MockQueryObserverResult<TData> {
|
||||
return createMockQueryState<TData>({ status: 'pending', data: undefined, error: undefined });
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an error query state
|
||||
*/
|
||||
export function createErrorState<TError = Error>(
|
||||
error: TError,
|
||||
): MockQueryObserverResult<unknown, TError> {
|
||||
return createMockQueryState<unknown, TError>({ status: 'error', data: undefined, error });
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a success query state
|
||||
*/
|
||||
export function createSuccessState<TData>(data: TData): MockQueryObserverResult<TData> {
|
||||
return createMockQueryState<TData>({ status: 'success', data, error: undefined });
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// FONT STORE MOCKS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Mock UnifiedFontStore state
|
||||
*/
|
||||
export interface MockFontStoreState {
|
||||
/** All cached fonts */
|
||||
fonts: Record<string, UnifiedFont>;
|
||||
/** Current page */
|
||||
page: number;
|
||||
/** Total pages available */
|
||||
totalPages: number;
|
||||
/** Items per page */
|
||||
limit: number;
|
||||
/** Total font count */
|
||||
total: number;
|
||||
/** Loading state */
|
||||
isLoading: boolean;
|
||||
/** Error state */
|
||||
error: Error | null;
|
||||
/** Search query */
|
||||
searchQuery: string;
|
||||
/** Selected provider */
|
||||
provider: 'google' | 'fontshare' | 'all';
|
||||
/** Selected category */
|
||||
category: string | null;
|
||||
/** Selected subset */
|
||||
subset: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a mock font store state
|
||||
*/
|
||||
export function createMockFontStoreState(
|
||||
options: Partial<MockFontStoreState> = {},
|
||||
): MockFontStoreState {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 24,
|
||||
isLoading = false,
|
||||
error = null,
|
||||
searchQuery = '',
|
||||
provider = 'all',
|
||||
category = null,
|
||||
subset = null,
|
||||
} = options;
|
||||
|
||||
// Generate mock fonts if not provided
|
||||
const mockFonts = options.fonts ?? Object.fromEntries(
|
||||
Object.values(UNIFIED_FONTS).map(font => [font.id, font]),
|
||||
);
|
||||
|
||||
const fontArray = Object.values(mockFonts);
|
||||
const total = options.total ?? fontArray.length;
|
||||
const totalPages = options.totalPages ?? Math.ceil(total / limit);
|
||||
|
||||
return {
|
||||
fonts: mockFonts,
|
||||
page,
|
||||
totalPages,
|
||||
limit,
|
||||
total,
|
||||
isLoading,
|
||||
error,
|
||||
searchQuery,
|
||||
provider,
|
||||
category,
|
||||
subset,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Preset font store states
|
||||
*/
|
||||
export const MOCK_FONT_STORE_STATES = {
|
||||
/** Initial loading state */
|
||||
loading: createMockFontStoreState({
|
||||
isLoading: true,
|
||||
fonts: {},
|
||||
total: 0,
|
||||
page: 1,
|
||||
}),
|
||||
|
||||
/** Empty state (no fonts found) */
|
||||
empty: createMockFontStoreState({
|
||||
fonts: {},
|
||||
total: 0,
|
||||
page: 1,
|
||||
isLoading: false,
|
||||
}),
|
||||
|
||||
/** First page with fonts */
|
||||
firstPage: createMockFontStoreState({
|
||||
fonts: Object.fromEntries(
|
||||
Object.values(UNIFIED_FONTS).slice(0, 10).map(font => [font.id, font]),
|
||||
),
|
||||
total: 50,
|
||||
page: 1,
|
||||
limit: 10,
|
||||
totalPages: 5,
|
||||
isLoading: false,
|
||||
}),
|
||||
|
||||
/** Second page with fonts */
|
||||
secondPage: createMockFontStoreState({
|
||||
fonts: Object.fromEntries(
|
||||
Object.values(UNIFIED_FONTS).slice(10, 20).map(font => [font.id, font]),
|
||||
),
|
||||
total: 50,
|
||||
page: 2,
|
||||
limit: 10,
|
||||
totalPages: 5,
|
||||
isLoading: false,
|
||||
}),
|
||||
|
||||
/** Last page with fonts */
|
||||
lastPage: createMockFontStoreState({
|
||||
fonts: Object.fromEntries(
|
||||
Object.values(UNIFIED_FONTS).slice(0, 5).map(font => [font.id, font]),
|
||||
),
|
||||
total: 25,
|
||||
page: 3,
|
||||
limit: 10,
|
||||
totalPages: 3,
|
||||
isLoading: false,
|
||||
}),
|
||||
|
||||
/** Error state */
|
||||
error: createMockFontStoreState({
|
||||
fonts: {},
|
||||
error: new Error('Failed to load fonts'),
|
||||
total: 0,
|
||||
page: 1,
|
||||
isLoading: false,
|
||||
}),
|
||||
|
||||
/** With search query */
|
||||
withSearch: createMockFontStoreState({
|
||||
fonts: Object.fromEntries(
|
||||
Object.values(UNIFIED_FONTS).slice(0, 3).map(font => [font.id, font]),
|
||||
),
|
||||
total: 3,
|
||||
page: 1,
|
||||
isLoading: false,
|
||||
searchQuery: 'Roboto',
|
||||
}),
|
||||
|
||||
/** Filtered by category */
|
||||
filteredByCategory: createMockFontStoreState({
|
||||
fonts: Object.fromEntries(
|
||||
Object.values(UNIFIED_FONTS)
|
||||
.filter(f => f.category === 'serif')
|
||||
.slice(0, 5)
|
||||
.map(font => [font.id, font]),
|
||||
),
|
||||
total: 5,
|
||||
page: 1,
|
||||
isLoading: false,
|
||||
category: 'serif',
|
||||
}),
|
||||
|
||||
/** Filtered by provider */
|
||||
filteredByProvider: createMockFontStoreState({
|
||||
fonts: Object.fromEntries(
|
||||
Object.values(UNIFIED_FONTS)
|
||||
.filter(f => f.provider === 'google')
|
||||
.slice(0, 5)
|
||||
.map(font => [font.id, font]),
|
||||
),
|
||||
total: 5,
|
||||
page: 1,
|
||||
isLoading: false,
|
||||
provider: 'google',
|
||||
}),
|
||||
|
||||
/** Large dataset */
|
||||
largeDataset: createMockFontStoreState({
|
||||
fonts: Object.fromEntries(
|
||||
generateMockFonts(50).map(font => [font.id, font]),
|
||||
),
|
||||
total: 500,
|
||||
page: 1,
|
||||
limit: 50,
|
||||
totalPages: 10,
|
||||
isLoading: false,
|
||||
}),
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// MOCK STORE OBJECT
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Create a mock store object that mimics TanStack Query behavior
|
||||
* Useful for components that subscribe to store properties
|
||||
*/
|
||||
export function createMockStore<T>(config: {
|
||||
data?: T;
|
||||
isLoading?: boolean;
|
||||
isError?: boolean;
|
||||
error?: Error;
|
||||
isFetching?: boolean;
|
||||
}) {
|
||||
const {
|
||||
data,
|
||||
isLoading = false,
|
||||
isError = false,
|
||||
error,
|
||||
isFetching = false,
|
||||
} = config;
|
||||
|
||||
return {
|
||||
get data() {
|
||||
return data;
|
||||
},
|
||||
get isLoading() {
|
||||
return isLoading;
|
||||
},
|
||||
get isError() {
|
||||
return isError;
|
||||
},
|
||||
get error() {
|
||||
return error;
|
||||
},
|
||||
get isFetching() {
|
||||
return isFetching;
|
||||
},
|
||||
get isSuccess() {
|
||||
return !isLoading && !isError && data !== undefined;
|
||||
},
|
||||
get status() {
|
||||
if (isLoading) return 'pending';
|
||||
if (isError) return 'error';
|
||||
return 'success';
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Preset mock stores
|
||||
*/
|
||||
export const MOCK_STORES = {
|
||||
/** Font store in loading state */
|
||||
loadingFontStore: createMockStore<UnifiedFont[]>({
|
||||
isLoading: true,
|
||||
data: undefined,
|
||||
}),
|
||||
|
||||
/** Font store with fonts loaded */
|
||||
successFontStore: createMockStore<UnifiedFont[]>({
|
||||
data: Object.values(UNIFIED_FONTS),
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
}),
|
||||
|
||||
/** Font store with error */
|
||||
errorFontStore: createMockStore<UnifiedFont[]>({
|
||||
data: undefined,
|
||||
isLoading: false,
|
||||
isError: true,
|
||||
error: new Error('Failed to load fonts'),
|
||||
}),
|
||||
|
||||
/** Font store with empty results */
|
||||
emptyFontStore: createMockStore<UnifiedFont[]>({
|
||||
data: [],
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
}),
|
||||
|
||||
/**
|
||||
* Create a mock UnifiedFontStore-like object
|
||||
* Note: This is a simplified mock for Storybook use
|
||||
*/
|
||||
unifiedFontStore: (state: Partial<MockFontStoreState> = {}) => {
|
||||
const mockState = createMockFontStoreState(state);
|
||||
return {
|
||||
// State properties
|
||||
get fonts() {
|
||||
return mockState.fonts;
|
||||
},
|
||||
get page() {
|
||||
return mockState.page;
|
||||
},
|
||||
get totalPages() {
|
||||
return mockState.totalPages;
|
||||
},
|
||||
get limit() {
|
||||
return mockState.limit;
|
||||
},
|
||||
get total() {
|
||||
return mockState.total;
|
||||
},
|
||||
get isLoading() {
|
||||
return mockState.isLoading;
|
||||
},
|
||||
get error() {
|
||||
return mockState.error;
|
||||
},
|
||||
get searchQuery() {
|
||||
return mockState.searchQuery;
|
||||
},
|
||||
get provider() {
|
||||
return mockState.provider;
|
||||
},
|
||||
get category() {
|
||||
return mockState.category;
|
||||
},
|
||||
get subset() {
|
||||
return mockState.subset;
|
||||
},
|
||||
// Methods (no-op for Storybook)
|
||||
nextPage: () => {},
|
||||
prevPage: () => {},
|
||||
goToPage: (_page: number) => {},
|
||||
setLimit: (_limit: number) => {},
|
||||
setProvider: (_provider: typeof mockState.provider) => {},
|
||||
setCategory: (_category: string | null) => {},
|
||||
setSubset: (_subset: string | null) => {},
|
||||
setSearch: (_query: string) => {},
|
||||
resetFilters: () => {},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// REACTIVE STATE MOCKS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Create a reactive state object using Svelte 5 runes pattern
|
||||
* Useful for stories that need reactive state
|
||||
*
|
||||
* Note: This uses plain JavaScript objects since Svelte runes
|
||||
* only work in .svelte files. For Storybook, this provides
|
||||
* a similar API for testing.
|
||||
*/
|
||||
export function createMockReactiveState<T>(initialValue: T) {
|
||||
let value = initialValue;
|
||||
|
||||
return {
|
||||
get value() {
|
||||
return value;
|
||||
},
|
||||
set value(newValue: T) {
|
||||
value = newValue;
|
||||
},
|
||||
update(fn: (current: T) => T) {
|
||||
value = fn(value);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock comparison store for ComparisonSlider component
|
||||
*/
|
||||
export function createMockComparisonStore(config: {
|
||||
fontA?: UnifiedFont;
|
||||
fontB?: UnifiedFont;
|
||||
text?: string;
|
||||
} = {}) {
|
||||
const { fontA, fontB, text = 'The quick brown fox jumps over the lazy dog.' } = config;
|
||||
|
||||
return {
|
||||
get fontA() {
|
||||
return fontA ?? UNIFIED_FONTS.roboto;
|
||||
},
|
||||
get fontB() {
|
||||
return fontB ?? UNIFIED_FONTS.openSans;
|
||||
},
|
||||
get text() {
|
||||
return text;
|
||||
},
|
||||
// Methods (no-op for Storybook)
|
||||
setFontA: (_font: UnifiedFont | undefined) => {},
|
||||
setFontB: (_font: UnifiedFont | undefined) => {},
|
||||
setText: (_text: string) => {},
|
||||
swapFonts: () => {},
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// MOCK DATA GENERATORS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Generate paginated font data
|
||||
*/
|
||||
export function generatePaginatedFonts(
|
||||
totalCount: number,
|
||||
page: number,
|
||||
limit: number,
|
||||
): {
|
||||
fonts: UnifiedFont[];
|
||||
page: number;
|
||||
totalPages: number;
|
||||
total: number;
|
||||
hasNextPage: boolean;
|
||||
hasPrevPage: boolean;
|
||||
} {
|
||||
const totalPages = Math.ceil(totalCount / limit);
|
||||
const startIndex = (page - 1) * limit;
|
||||
const endIndex = Math.min(startIndex + limit, totalCount);
|
||||
|
||||
return {
|
||||
fonts: generateMockFonts(endIndex - startIndex).map((font, i) => ({
|
||||
...font,
|
||||
id: `font-${startIndex + i + 1}`,
|
||||
name: `Font ${startIndex + i + 1}`,
|
||||
})),
|
||||
page,
|
||||
totalPages,
|
||||
total: totalCount,
|
||||
hasNextPage: page < totalPages,
|
||||
hasPrevPage: page > 1,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create mock API response for fonts
|
||||
*/
|
||||
export function createMockFontApiResponse(config: {
|
||||
fonts?: UnifiedFont[];
|
||||
total?: number;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
} = {}) {
|
||||
const fonts = config.fonts ?? Object.values(UNIFIED_FONTS);
|
||||
const total = config.total ?? fonts.length;
|
||||
const page = config.page ?? 1;
|
||||
const limit = config.limit ?? fonts.length;
|
||||
|
||||
return {
|
||||
data: fonts,
|
||||
meta: {
|
||||
total,
|
||||
page,
|
||||
limit,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
hasNextPage: page < Math.ceil(total / limit),
|
||||
hasPrevPage: page > 1,
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user