Files
bDS/tests/renderer/store/appStore.test.ts
2026-02-16 12:11:27 +01:00

202 lines
5.8 KiB
TypeScript

/**
* Tests for the app store
* Validates state management behavior for posts, dirty tracking, and UI state
*/
import { describe, it, expect, expectTypeOf, beforeEach } from 'vitest';
import { useAppStore, ProjectData, PostData, MediaData, TaskProgress } from '../../../src/renderer/store/appStore';
import type {
ProjectData as SharedProjectData,
PostData as SharedPostData,
MediaData as SharedMediaData,
TaskProgress as SharedTaskProgress,
} from '../../../src/main/shared/electronApi';
// Helper to create a mock post
const createMockPost = (overrides: Partial<PostData> = {}): PostData => ({
id: `post-${Date.now()}-${Math.random().toString(36).substring(7)}`,
projectId: 'project-1',
title: 'Test Post',
slug: 'test-post',
content: '# Test Content',
status: 'draft',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
tags: [],
categories: [],
...overrides,
});
// Direct store access without React rendering
const getStore = () => useAppStore.getState();
const setState = useAppStore.setState;
describe('AppStore', () => {
beforeEach(() => {
// Reset store state before each test
setState({
posts: [],
selectedPostId: null,
dirtyPosts: new Set(),
});
});
describe('Post Management', () => {
it('should add a post to the store', () => {
const post = createMockPost({ id: 'post-1', title: 'New Post' });
getStore().addPost(post);
expect(getStore().posts).toHaveLength(1);
expect(getStore().posts[0].title).toBe('New Post');
});
it('should update an existing post in the store', () => {
const post = createMockPost({ id: 'post-1', title: 'Original Title' });
getStore().addPost(post);
getStore().updatePost('post-1', { title: 'Updated Title' });
expect(getStore().posts).toHaveLength(1);
expect(getStore().posts[0].title).toBe('Updated Title');
});
it('should preserve other post fields when updating', () => {
const post = createMockPost({
id: 'post-1',
title: 'Original',
content: 'Original Content',
tags: ['tag1'],
});
getStore().addPost(post);
getStore().updatePost('post-1', { title: 'Updated Title' });
expect(getStore().posts[0].content).toBe('Original Content');
expect(getStore().posts[0].tags).toEqual(['tag1']);
});
it('should remove a post from the store', () => {
const post = createMockPost({ id: 'post-1' });
getStore().addPost(post);
getStore().removePost('post-1');
expect(getStore().posts).toHaveLength(0);
});
it('should clear selectedPostId when the selected post is removed', () => {
const post = createMockPost({ id: 'post-1' });
getStore().addPost(post);
getStore().setSelectedPost('post-1');
expect(getStore().selectedPostId).toBe('post-1');
getStore().removePost('post-1');
expect(getStore().selectedPostId).toBeNull();
});
});
describe('Dirty Tracking', () => {
it('should mark a post as dirty', () => {
getStore().markDirty('post-1');
expect(getStore().isDirty('post-1')).toBe(true);
});
it('should mark a post as clean', () => {
getStore().markDirty('post-1');
getStore().markClean('post-1');
expect(getStore().isDirty('post-1')).toBe(false);
});
it('should track multiple dirty posts independently', () => {
getStore().markDirty('post-1');
getStore().markDirty('post-2');
expect(getStore().isDirty('post-1')).toBe(true);
expect(getStore().isDirty('post-2')).toBe(true);
getStore().markClean('post-1');
expect(getStore().isDirty('post-1')).toBe(false);
expect(getStore().isDirty('post-2')).toBe(true);
});
it('should return false for non-dirty posts', () => {
expect(getStore().isDirty('non-existent-post')).toBe(false);
});
});
describe('Post Selection', () => {
it('should set selected post ID', () => {
getStore().setSelectedPost('post-1');
expect(getStore().selectedPostId).toBe('post-1');
});
it('should clear selected post ID when set to null', () => {
getStore().setSelectedPost('post-1');
getStore().setSelectedPost(null);
expect(getStore().selectedPostId).toBeNull();
});
});
describe('UI State', () => {
it('should toggle sidebar visibility', () => {
const initialState = getStore().sidebarVisible;
getStore().toggleSidebar();
expect(getStore().sidebarVisible).toBe(!initialState);
});
it('should set active view', () => {
getStore().setActiveView('media');
expect(getStore().activeView).toBe('media');
});
it('should set preferred editor mode', () => {
getStore().setPreferredEditorMode('markdown');
expect(getStore().preferredEditorMode).toBe('markdown');
});
it('should default git diff preferences to wrapped inline and visible unchanged regions', () => {
expect(getStore().gitDiffPreferences).toEqual({
wordWrap: true,
viewStyle: 'inline',
hideUnchangedRegions: false,
});
});
it('should update git diff preferences', () => {
getStore().setGitDiffPreferences({
wordWrap: false,
viewStyle: 'side-by-side',
hideUnchangedRegions: true,
});
expect(getStore().gitDiffPreferences).toEqual({
wordWrap: false,
viewStyle: 'side-by-side',
hideUnchangedRegions: true,
});
});
});
describe('Type Contract', () => {
it('should keep store data types aligned with the shared Electron API contract', () => {
expectTypeOf<ProjectData>().toEqualTypeOf<SharedProjectData>();
expectTypeOf<PostData>().toEqualTypeOf<SharedPostData>();
expectTypeOf<MediaData>().toEqualTypeOf<SharedMediaData>();
expectTypeOf<TaskProgress>().toEqualTypeOf<SharedTaskProgress>();
});
});
});