Skip to content

Commit

Permalink
feat(edit-content) clean up, add more test
Browse files Browse the repository at this point in the history
  • Loading branch information
oidacra committed Nov 19, 2024
1 parent 3ddf494 commit fd6dac3
Show file tree
Hide file tree
Showing 13 changed files with 501 additions and 388 deletions.
Original file line number Diff line number Diff line change
@@ -1,285 +1,60 @@
import {
createServiceFactory,
mockProvider,
SpectatorService,
SpyObject
} from '@ngneat/spectator/jest';
import { of, throwError } from 'rxjs';

import { HttpErrorResponse } from '@angular/common/http';
import { fakeAsync, tick } from '@angular/core/testing';
import { ActivatedRoute, Router } from '@angular/router';

import { MessageService } from 'primeng/api';

import { ActivatedRoute } from '@angular/router';
import {
DotContentTypeService,
DotFireActionOptions,
DotHttpErrorManagerService,
DotMessageService,
DotRenderMode,
DotWorkflowActionsFireService,
DotWorkflowsActionsService,
DotWorkflowService
} from '@dotcms/data-access';
import {
ComponentStatus,
DotCMSContentlet,
DotCMSContentType,
DotCMSWorkflowAction
} from '@dotcms/dotcms-models';
import { MOCK_SINGLE_WORKFLOW_ACTIONS, MockDotMessageService } from '@dotcms/utils-testing';

import { DotEditContentStore } from './edit-content.store';

import { ComponentStatus } from '@dotcms/dotcms-models';
import { createServiceFactory, mockProvider, SpectatorService } from '@ngneat/spectator/jest';
import { MessageService } from 'primeng/api';
import { DotEditContentService } from '../../../services/dot-edit-content.service';
import { CONTENT_TYPE_MOCK } from '../../../utils/mocks';

const messageServiceMock = new MockDotMessageService({
'edit.content.success.workflow.message': 'Your changes have being applied.',
success: 'Success'
});
import { DotEditContentStore } from './edit-content.store';

describe('DotEditContentStore', () => {
let spectator: SpectatorService<InstanceType<typeof DotEditContentStore>>;
let store: InstanceType<typeof DotEditContentStore>;

let contentTypeService: SpyObject<DotContentTypeService>;

let dotHttpErrorManagerService: SpyObject<DotHttpErrorManagerService>;
let dotEditContentService: SpyObject<DotEditContentService>;

let mockActivatedRouteParams: { [key: string]: unknown };
let router: SpyObject<Router>;

let workflowActionsService: SpyObject<DotWorkflowsActionsService>;
let workflowActionsFireService: SpyObject<DotWorkflowActionsFireService>;
let messageService: SpyObject<MessageService>;

const createService = createServiceFactory({
service: DotEditContentStore,
mocks: [
DotWorkflowActionsFireService,
DotContentTypeService,
DotEditContentService,
DotHttpErrorManagerService,
DotWorkflowsActionsService,
DotWorkflowService,
MessageService
],
providers: [
{
provide: ActivatedRoute,
useValue: {
get snapshot() {
return { params: mockActivatedRouteParams };
return { params: {} };
}
}
},

mockProvider(Router, {
navigate: jest.fn().mockReturnValue(Promise.resolve(true))
}),
{
provide: DotMessageService,
useValue: messageServiceMock
}
mockProvider(DotHttpErrorManagerService),
mockProvider(DotEditContentService),
mockProvider(DotContentTypeService),
mockProvider(DotWorkflowsActionsService),
mockProvider(DotWorkflowService),
mockProvider(DotWorkflowActionsFireService),
mockProvider(MessageService),
mockProvider(DotMessageService)
]
});

beforeEach(() => {
mockActivatedRouteParams = {};

spectator = createService();

store = spectator.service;
contentTypeService = spectator.inject(DotContentTypeService);
dotHttpErrorManagerService = spectator.inject(DotHttpErrorManagerService);
workflowActionsService = spectator.inject(DotWorkflowsActionsService);
workflowActionsFireService = spectator.inject(DotWorkflowActionsFireService);
dotEditContentService = spectator.inject(DotEditContentService);
messageService = spectator.inject(MessageService);

router = spectator.inject(Router);
});

afterEach(() => {
jest.resetAllMocks();
});

it('should create the store', () => {
expect(spectator.service).toBeDefined();
});

describe('initializeNewContent', () => {
it('should initialize new content successfully', () => {
const testContentType = 'testContentType';

contentTypeService.getContentType.mockReturnValue(of(CONTENT_TYPE_MOCK));
workflowActionsService.getDefaultActions.mockReturnValue(
of(MOCK_SINGLE_WORKFLOW_ACTIONS)
);
workflowActionsService.getWorkFlowActions.mockReturnValue(
of(MOCK_SINGLE_WORKFLOW_ACTIONS)
);

store.initializeNewContent(testContentType);

// use the proper contentType for get the data
expect(contentTypeService.getContentType).toHaveBeenCalledWith(testContentType);
expect(workflowActionsService.getDefaultActions).toHaveBeenCalledWith(testContentType);

expect(store.contentType()).toEqual(CONTENT_TYPE_MOCK);

expect(store.state()).toBe(ComponentStatus.LOADED);
expect(store.error()).toBeNull();
});

it('should handle error when initializing new content', fakeAsync(() => {
const mockError = new HttpErrorResponse({ status: 404, statusText: 'Not Found' });

contentTypeService.getContentType.mockReturnValue(throwError(() => mockError));
workflowActionsService.getDefaultActions.mockReturnValue(
of(MOCK_SINGLE_WORKFLOW_ACTIONS)
);

store.initializeNewContent('testContentType');

expect(store.error()).toBe('Error initializing content');
expect(store.state()).toBe(ComponentStatus.ERROR);
expect(dotHttpErrorManagerService.handle).toHaveBeenCalled();
}));
});

describe('initializeExistingContent', () => {
const testInode = '123-test-inode';
it('should initialize existing content successfully', () => {
const mockContentlet = {
inode: testInode,
contentType: 'testContentType'
} as DotCMSContentlet;

const mockContentType = {
id: '1',
name: 'Test Content Type'
} as DotCMSContentType;

const mockActions = [{ id: '1', name: 'Test Action' }] as DotCMSWorkflowAction[];

dotEditContentService.getContentById.mockReturnValue(of(mockContentlet));
contentTypeService.getContentType.mockReturnValue(of(mockContentType));
workflowActionsService.getByInode.mockReturnValue(of(mockActions));
workflowActionsService.getWorkFlowActions.mockReturnValue(
of(MOCK_SINGLE_WORKFLOW_ACTIONS)
);

store.initializeExistingContent(testInode);

expect(dotEditContentService.getContentById).toHaveBeenCalledWith(testInode);
expect(contentTypeService.getContentType).toHaveBeenCalledWith(
mockContentlet.contentType
);
expect(workflowActionsService.getByInode).toHaveBeenCalledWith(
testInode,
expect.anything()
);

expect(store.contentlet()).toEqual(mockContentlet);
expect(store.contentType()).toEqual(mockContentType);

expect(store.state()).toBe(ComponentStatus.LOADED);
expect(store.error()).toBe(null);
});

it('should handle error when initializing existing content', fakeAsync(() => {
const mockError = new HttpErrorResponse({ status: 404, statusText: 'Not Found' });

dotEditContentService.getContentById.mockReturnValue(throwError(() => mockError));

store.initializeExistingContent(testInode);
tick();

expect(dotEditContentService.getContentById).toHaveBeenCalledWith(testInode);
expect(dotHttpErrorManagerService.handle).toHaveBeenCalled();
expect(router.navigate).toHaveBeenCalledWith(['/c/content']);

expect(store.state()).toBe(ComponentStatus.ERROR);
}));
});

describe('fireWorkflowAction', () => {
const mockOptions: DotFireActionOptions<{ [key: string]: string | object }> = {
inode: '123',
actionId: 'publish'
};

it('should fire workflow action successfully', fakeAsync(() => {
const mockContentlet = { inode: '456', contentType: 'testType' } as DotCMSContentlet;
const mockActions = [{ id: '1', name: 'Test Action' }] as DotCMSWorkflowAction[];

workflowActionsFireService.fireTo.mockReturnValue(of(mockContentlet));
workflowActionsService.getByInode.mockReturnValue(of(mockActions));

store.fireWorkflowAction(mockOptions);
tick();

expect(store.state()).toBe(ComponentStatus.LOADED);
expect(store.contentlet()).toEqual(mockContentlet);

expect(store.error()).toBeNull();

expect(workflowActionsFireService.fireTo).toHaveBeenCalledWith(mockOptions);
expect(workflowActionsService.getByInode).toHaveBeenCalledWith(
mockContentlet.inode,
DotRenderMode.EDITING
);
expect(router.navigate).toHaveBeenCalledWith(['/content', mockContentlet.inode], {
replaceUrl: true,
queryParamsHandling: 'preserve'
});

expect(messageService.add).toHaveBeenCalledWith({
severity: 'success',
summary: 'Success',
detail: 'Your changes have being applied.'
});
}));

it('should handle error when firing workflow action', fakeAsync(() => {
const mockError = new HttpErrorResponse({
status: 500,
statusText: 'Internal Server Error'
});

workflowActionsFireService.fireTo.mockReturnValue(throwError(() => mockError));

store.fireWorkflowAction(mockOptions);
tick();

expect(store.state()).toBe(ComponentStatus.LOADED);
expect(store.error()).toBe('Error firing workflow action');
expect(dotHttpErrorManagerService.handle).toHaveBeenCalled();
}));

it('should navigate to content list if contentlet has no inode', fakeAsync(() => {
const mockContentletWithoutInode = { contentType: 'testType' } as DotCMSContentlet;

workflowActionsFireService.fireTo.mockReturnValue(of(mockContentletWithoutInode));

store.fireWorkflowAction(mockOptions);
tick();

expect(router.navigate).toHaveBeenCalledWith(['/c/content']);
}));
it('should create store with initial state', () => {
expect(store.state()).toBe(ComponentStatus.INIT);
expect(store.error()).toBeNull();
});

describe('toggleSidebar', () => {
it('should toggle sidebar state', () => {
expect(store.showSidebar()).toBe(true);
store.toggleSidebar();
expect(store.showSidebar()).toBe(false);
store.toggleSidebar();
expect(store.showSidebar()).toBe(true);
});
it('should compose with all required features', () => {
// Verify features are composed into the store
expect(store.contentType).toBeDefined();
expect(store.contentlet).toBeDefined();
expect(store.showSidebar).toBeDefined();
expect(store.showWorkflowActions).toBeDefined();
expect(store.information).toBeDefined();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import { withInformation } from './features/information.feature';
import { withSidebar } from './features/sidebar.feature';
import { withWorkflow } from './features/workflow.feature';

export interface EditContentState {
export interface EditContentRootState {
state: ComponentStatus;
error: string | null;
}

const initialState: EditContentState = {
export const initialState: EditContentRootState = {
state: ComponentStatus.INIT,
error: null
};
Expand All @@ -26,7 +26,7 @@ const initialState: EditContentState = {
* related to content editing and workflow actions.
*/
export const DotEditContentStore = signalStore(
withState<EditContentState>(initialState),
withState(initialState),
withContent(),
withSidebar(),
withInformation(),
Expand Down
Loading

0 comments on commit fd6dac3

Please sign in to comment.