Skip to content

Commit

Permalink
chore(edit-content): fix unit tests #30062
Browse files Browse the repository at this point in the history
  • Loading branch information
nicobytes committed Oct 14, 2024
1 parent f368b9f commit e83ea71
Show file tree
Hide file tree
Showing 7 changed files with 279 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from '@ngneat/spectator/jest';
import { of } from 'rxjs';

import { provideHttpClient } from '@angular/common/http';
import { Component, NgZone } from '@angular/core';
import { fakeAsync, tick } from '@angular/core/testing';
import {
Expand All @@ -22,6 +23,7 @@ import { By } from '@angular/platform-browser';

import { ButtonModule, Button } from 'primeng/button';
import { DialogModule } from 'primeng/dialog';
import { DialogService } from 'primeng/dynamicdialog';

import {
DotAiService,
Expand All @@ -31,7 +33,7 @@ import {
} from '@dotcms/data-access';
import { DotCMSTempFile } from '@dotcms/dotcms-models';
import { DotEditContentBinaryFieldComponent } from '@dotcms/edit-content';
import { DotAiImagePromptStore, DropZoneErrorType, DropZoneFileEvent } from '@dotcms/ui';
import { DropZoneErrorType, DropZoneFileEvent } from '@dotcms/ui';
import { dotcmsContentletMock } from '@dotcms/utils-testing';

import { DotBinaryFieldPreviewComponent } from './components/dot-binary-field-preview/dot-binary-field-preview.component';
Expand Down Expand Up @@ -90,14 +92,15 @@ describe('DotEditContentBinaryFieldComponent', () => {
component: DotEditContentBinaryFieldComponent,
componentProviders: [
DotBinaryFieldStore,
DotAiImagePromptStore,
DotBinaryFieldEditImageService,
DotAiService
DotAiService,
DialogService
],
componentViewProviders: [
{ provide: ControlContainer, useValue: createFormGroupDirectiveMock() }
],
providers: [
provideHttpClient(),
DotBinaryFieldValidatorService,
{
provide: DotLicenseService,
Expand Down Expand Up @@ -586,7 +589,7 @@ describe('DotEditContentBinaryFieldComponent - ControlValueAccessor', () => {
ReactiveFormsModule,
DotEditContentBinaryFieldComponent
],
providers: [DotAiService, DotAiImagePromptStore]
providers: [DotAiService, provideHttpClient()]
});

beforeEach(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { MonacoEditorConstructionOptions, MonacoEditorModule } from '@materia-ui/ngx-monaco-editor';

import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
import {
AfterViewInit,
ChangeDetectionStrategy,
Expand Down Expand Up @@ -87,7 +86,6 @@ type SystemOptionsType = {
DotMessagePipe,
DotBinaryFieldUiMessageComponent,
DotSpinnerModule,
HttpClientModule,
DotBinaryFieldEditorComponent,
InputTextModule,
DotBinaryFieldUrlModeComponent,
Expand Down Expand Up @@ -271,6 +269,16 @@ export class DotEditContentBinaryFieldComponent
this.#dotBinaryFieldStore.setMode(mode);
}

/**
* Opens a dialog for AI Image Prompt using the DotAIImagePromptComponent.
* The dialog has various configurations such as header, appendTo, closeOnEscape, draggable,
* keepInViewport, maskStyleClass, resizable, modal, width, and style.
*
* When the dialog is closed, it filters the selected image and if an image is selected,
* it parses the image to a temporary file and sets it in the dotBinaryFieldStore.
*
* @private
*/
openAIImagePrompt() {
this.#dialogRef = this.#dialogService.open(DotAIImagePromptComponent, {
header: 'AI Image Prompt',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,110 +1,91 @@
import { byTestId, createComponentFactory, mockProvider, Spectator } from '@ngneat/spectator';
import {
byTestId,
createComponentFactory,
mockProvider,
Spectator,
SpyObject
} from '@ngneat/spectator';
import { patchState } from '@ngrx/signals';
import { of } from 'rxjs';

import { HttpClientTestingModule } from '@angular/common/http/testing';
import { fakeAsync } from '@angular/core/testing';
import { provideHttpClient } from '@angular/common/http';

import { ConfirmationService } from 'primeng/api';
import { Dialog } from 'primeng/dialog';
import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';

import {
PromptType,
AIImagePrompt,
DotAIImageOrientation,
DotGeneratedAIImage
} from '@dotcms/dotcms-models';
import { DotAiService } from '@dotcms/data-access';
import { PromptType, AIImagePrompt, DotAIImageOrientation } from '@dotcms/dotcms-models';

import { DotAIImagePromptComponent } from './ai-image-prompt.component';
import { DotAiImagePromptStore } from './ai-image-prompt.store';
import { AiImagePromptFormComponent } from './components/ai-image-prompt-form/ai-image-prompt-form.component';
import { DotAiImagePromptStore } from './store/ai-image-prompt.store';
import { MOCK_AI_IMAGE_CONTENT, MOCK_GENERATED_AI_IMAGE } from './utils/mocks';

describe('DotAIImagePromptComponent', () => {
let spectator: Spectator<DotAIImagePromptComponent>;
let store: DotAiImagePromptStore;

const imagesMock: DotGeneratedAIImage[] = [
{ name: 'image1', url: 'image_url' },
{ name: 'image2', url: 'image_url_2' }
] as unknown as DotGeneratedAIImage[];
let store: InstanceType<typeof DotAiImagePromptStore>;
let dynamicDialogRef: SpyObject<DynamicDialogRef>;
let dotAiService: SpyObject<DotAiService>;
let confirmationService: SpyObject<ConfirmationService>;

const createComponent = createComponentFactory({
component: DotAIImagePromptComponent,
providers: [
{
provide: DotAiImagePromptStore,
useValue: {
vm$: of({
showDialog: true,
isLoading: false,
images: imagesMock,
galleryActiveIndex: 0,
orientation: DotAIImageOrientation.VERTICAL
}),
generateImage: jasmine.createSpy('generateImage'),
hideDialog: jasmine.createSpy('hideDialog'),
patchState: jasmine.createSpy('patchState'),
cleanError: jasmine.createSpy('cleanError'),
setSelectedImage: jasmine.createSpy('setSelectedImage')
}
},
mockProvider(ConfirmationService)
componentProviders: [
DotAiImagePromptStore,
mockProvider(DynamicDialogRef),
mockProvider(DynamicDialogConfig),
ConfirmationService
],
imports: [HttpClientTestingModule]
providers: [provideHttpClient(), mockProvider(DotAiService)]
});

beforeEach(() => {
spectator = createComponent();
store = spectator.inject(DotAiImagePromptStore);
store = spectator.inject(DotAiImagePromptStore, true);
dynamicDialogRef = spectator.inject(DynamicDialogRef, true);
dotAiService = spectator.inject(DotAiService, true);
confirmationService = spectator.inject(ConfirmationService, true);
});

it('should hide dialog', () => {
const dialog = spectator.query(Dialog);
dialog.onHide.emit('true');
expect(store.hideDialog).toHaveBeenCalled();
});

it('should the modal have 1040px in maxWidth', () => {
spectator.detectChanges();
const dialog = spectator.query(Dialog);
const width = dialog.style.width;
const maxWidth = dialog.style.maxWidth;
expect(width).toBe('90%');
expect(maxWidth).toBe('1040px');
it('should create', () => {
expect(spectator.component).toBeTruthy();
});

it('should generate image', () => {
const promptForm = spectator.query(AiImagePromptFormComponent);
const generateImageSpy = spyOn(store, 'generateImage');
dotAiService.generateAndPublishImage.and.returnValue(of(MOCK_AI_IMAGE_CONTENT));

const formMock: AIImagePrompt = {
text: 'Test',
type: PromptType.INPUT,
size: DotAIImageOrientation.VERTICAL
};
promptForm.valueChange.emit(formMock);
promptForm.generate.emit();
patchState(store, { formValue: formMock });

expect(store.generateImage).toHaveBeenCalledWith();
spectator.triggerEventHandler(AiImagePromptFormComponent, 'generate', null);
expect(generateImageSpy).toHaveBeenCalledWith();
});

it('should inset image', async () => {
it('should inset an image', async () => {
patchState(store, { images: [MOCK_GENERATED_AI_IMAGE] });
spectator.detectChanges();

const submitBtn = spectator.query(byTestId('submit-btn'));

spectator.click(submitBtn);
await spectator.fixture.whenStable();
expect(store.setSelectedImage).toHaveBeenCalled();
expect(dynamicDialogRef.close).toHaveBeenCalled();
});

it('should clear error on hide confirm', () => {
const dialog = spectator.query(Dialog);
dialog.onHide.emit('true');
expect(store.hideDialog).toHaveBeenCalled();
});
it('should call confirm dialog when try to close dialog', async () => {
patchState(store, { images: [MOCK_GENERATED_AI_IMAGE] });
const confirmSpy = spyOn(confirmationService, 'confirm');
spectator.detectChanges();

it('should call confirm dialog when try to close dialog', fakeAsync(() => {
const closeBtn = spectator.query(byTestId('close-btn'));
const spyCloseDialog = spyOn(spectator.component, 'closeDialog');

spectator.click(closeBtn);
spectator.tick();
expect(spyCloseDialog).toHaveBeenCalled();
}));
await spectator.fixture.whenStable();
expect(confirmSpy).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
import { DotMessageService } from '@dotcms/data-access';

import { DotMessagePipe } from './../../dot-message/dot-message.pipe';
import { DotAiImagePromptdStore } from './ai-image-prompt.store';
import { AiImagePromptFormComponent } from './components/ai-image-prompt-form/ai-image-prompt-form.component';
import { AiImagePromptGalleryComponent } from './components/ai-image-prompt-gallery/ai-image-prompt-gallery.component';
import { DotAiImagePromptStore } from './store/ai-image-prompt.store';

@Component({
selector: 'dot-ai-image-prompt',
Expand All @@ -29,13 +29,13 @@ import { AiImagePromptGalleryComponent } from './components/ai-image-prompt-gall
AiImagePromptFormComponent,
AiImagePromptGalleryComponent
],
providers: [FormGroupDirective, ConfirmationService, DotAiImagePromptdStore],
providers: [FormGroupDirective, ConfirmationService, DotAiImagePromptStore],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DotAIImagePromptComponent implements OnInit {
readonly #dotMessageService = inject(DotMessageService);
readonly #confirmationService = inject(ConfirmationService);
readonly store = inject(DotAiImagePromptdStore);
readonly store = inject(DotAiImagePromptStore);

readonly #dialogRef = inject(DynamicDialogRef);
readonly #dialogConfig = inject(DynamicDialogConfig<{ context: string }>);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { SpyObject, mockProvider } from '@ngneat/spectator';
import { patchState } from '@ngrx/signals';
import { of, throwError } from 'rxjs';

import { TestBed } from '@angular/core/testing';

import { DotAiService } from '@dotcms/data-access';
import { ComponentStatus, DotAIImageOrientation, PromptType } from '@dotcms/dotcms-models';

import { DotAiImagePromptStore } from './ai-image-prompt.store';

import { MOCK_AI_IMAGE_CONTENT, MOCK_GENERATED_AI_IMAGE } from '../utils/mocks';

describe('DotAiImagePromptStore', () => {
let store: InstanceType<typeof DotAiImagePromptStore>;
let dotAiService: SpyObject<DotAiService>;

beforeEach(() => {
TestBed.configureTestingModule({
providers: [DotAiImagePromptStore, mockProvider(DotAiService)]
});

store = TestBed.inject(DotAiImagePromptStore);
dotAiService = TestBed.inject(DotAiService) as SpyObject<DotAiService>;
});

it('should be created', () => {
expect(store).toBeTruthy();
});

describe('Initial State', () => {
it('should have the correct initial state', () => {
expect(store.status()).toBe(ComponentStatus.INIT);
expect(store.images()).toEqual([]);
expect(store.context()).toBeNull();
expect(store.galleryActiveIndex()).toBe(0);
expect(store.error()).toBeNull();
expect(store.formValue()).toEqual({
text: '',
type: PromptType.INPUT,
size: DotAIImageOrientation.HORIZONTAL
});
});
});

describe('Computed Properties', () => {
it('should compute isLoading correctly', () => {
expect(store.isLoading()).toBe(false);
patchState(store, { status: ComponentStatus.LOADING });
expect(store.isLoading()).toBe(true);
});

it('should compute hasContext correctly', () => {
expect(store.hasContext()).toBe(false);
patchState(store, { context: 'context' });
expect(store.hasContext()).toBe(true);
});

it('should compute currentImage correctly', () => {
expect(store.currentImage()).toBeUndefined();
patchState(store, {
images: [MOCK_GENERATED_AI_IMAGE]
});
expect(store.currentImage()).toEqual(MOCK_GENERATED_AI_IMAGE);
});
});

describe('Methods', () => {
it('should set galleryActiveIndex correctly', () => {
store.setGalleryActiveIndex(1);
expect(store.galleryActiveIndex()).toBe(1);
});

it('should set context correctly', () => {
store.setContext('new context');
expect(store.context()).toBe('new context');
});

it('should set formValue correctly', () => {
const formValue = {
text: 'new text',
type: PromptType.INPUT,
size: DotAIImageOrientation.VERTICAL
};
store.setFormValue(formValue);
expect(store.formValue()).toEqual(formValue);
});

it('should handle generateImage correctly on success', () => {
dotAiService.generateAndPublishImage.and.returnValue(of(MOCK_AI_IMAGE_CONTENT));

store.setFormValue({
text: 'prompt',
type: PromptType.INPUT,
size: DotAIImageOrientation.HORIZONTAL
});
store.generateImage();

expect(dotAiService.generateAndPublishImage).toHaveBeenCalledWith(
'prompt',
DotAIImageOrientation.HORIZONTAL
);
expect(store.status()).toBe(ComponentStatus.IDLE);
expect(store.images().length).toBe(1);
});

it('should handle generateImage correctly on error', () => {
dotAiService.generateAndPublishImage.and.returnValue(throwError('error'));

store.setFormValue({
text: 'prompt',
type: PromptType.INPUT,
size: DotAIImageOrientation.HORIZONTAL
});
store.generateImage();

expect(dotAiService.generateAndPublishImage).toHaveBeenCalledWith(
'prompt',
DotAIImageOrientation.HORIZONTAL
);
expect(store.status()).toBe(ComponentStatus.ERROR);
expect(store.error()).toBe('error');
});
});
});
Loading

0 comments on commit e83ea71

Please sign in to comment.