Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(edit-content): apply format to relationships #31048

Merged
merged 11 commits into from
Jan 3, 2025
30 changes: 30 additions & 0 deletions core-web/libs/dotcms-models/src/lib/dot-contentlet.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,33 @@ export interface DotContentletPermissions {
PUBLISH?: string[];
CAN_ADD_CHILDREN?: string[];
}

/**
* The depth of the contentlet.
*
* @enum {string}
* @property {string} ZERO - Without relationships
* @property {string} ONE - Retrieve the id of relationships
* @property {string} TWO - Retrieve relationships
* @property {string} THREE - Retrieve relationships with their relationships
*/
export enum DotContentletDepths {
/**
* Without relationships
*/
ZERO = '0',
/**
* Retrieve the id of relationships
*/
ONE = '1',
/**
* Retrieve relationships
*/
TWO = '2',
/**
* Retrieve relationships with their relationships
*/
THREE = '3'
}

export type DotContentletDepth = `${DotContentletDepths}`;
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
DotWorkflowsActionsService,
DotWorkflowService
} from '@dotcms/data-access';
import { DotCMSWorkflowAction } from '@dotcms/dotcms-models';
import { DotCMSWorkflowAction, DotContentletDepths } from '@dotcms/dotcms-models';
import { DotWorkflowActionsComponent } from '@dotcms/ui';
import {
DotFormatDateServiceMock,
Expand Down Expand Up @@ -122,7 +122,10 @@ describe('DotFormComponent', () => {
);
dotWorkflowService.getWorkflowStatus.mockReturnValue(of(MOCK_WORKFLOW_STATUS));

store.initializeExistingContent(MOCK_CONTENTLET_1_OR_2_TABS.inode); // called with the inode of the contentlet
store.initializeExistingContent({
inode: MOCK_CONTENTLET_1_OR_2_TABS.inode,
depth: DotContentletDepths.ONE
}); // called with the inode of the contentlet

spectator.detectChanges();
});
Expand Down Expand Up @@ -200,7 +203,10 @@ describe('DotFormComponent', () => {
);
dotWorkflowService.getWorkflowStatus.mockReturnValue(of(MOCK_WORKFLOW_STATUS));

store.initializeExistingContent(MOCK_CONTENTLET_1_OR_2_TABS.inode); // called with the inode of the contentlet
store.initializeExistingContent({
inode: MOCK_CONTENTLET_1_OR_2_TABS.inode,
depth: DotContentletDepths.ONE
}); // called with the inode of the contentlet
spectator.detectChanges();
});

Expand Down Expand Up @@ -293,7 +299,10 @@ describe('DotFormComponent', () => {
);
dotWorkflowService.getWorkflowStatus.mockReturnValue(of(MOCK_WORKFLOW_STATUS));

store.initializeExistingContent(MOCK_CONTENTLET_1_OR_2_TABS.inode);
store.initializeExistingContent({
inode: MOCK_CONTENTLET_1_OR_2_TABS.inode,
depth: DotContentletDepths.ONE
});
spectator.detectChanges();
});

Expand All @@ -307,7 +316,10 @@ describe('DotFormComponent', () => {
workflowActionsService.getWorkFlowActions.mockReturnValue(
of(MOCK_SINGLE_WORKFLOW_ACTIONS) // Single workflow actions trigger the show
);
store.initializeExistingContent('inode');
store.initializeExistingContent({
inode: 'inode',
depth: DotContentletDepths.ONE
});
spectator.detectChanges();

const workflowActions = spectator.query(DotWorkflowActionsComponent);
Expand All @@ -320,7 +332,10 @@ describe('DotFormComponent', () => {
of(MOCK_MULTIPLE_WORKFLOW_ACTIONS) // Multiple workflow actions trigger the hide
);

store.initializeExistingContent('inode');
store.initializeExistingContent({
inode: 'inode',
depth: DotContentletDepths.ONE
});
spectator.detectChanges();

const workflowActions = spectator.query(DotWorkflowActionsComponent);
Expand All @@ -334,7 +349,10 @@ describe('DotFormComponent', () => {
workflowActionsService.getWorkFlowActions.mockReturnValue(
of(MOCK_SINGLE_WORKFLOW_ACTIONS)
);
store.initializeExistingContent('inode');
store.initializeExistingContent({
inode: 'inode',
depth: DotContentletDepths.ONE
});
spectator.detectChanges();

const workflowActions = spectator.query(DotWorkflowActionsComponent);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { signalStore, withHooks, withState } from '@ngrx/signals';
import { inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

import { ComponentStatus } from '@dotcms/dotcms-models';
import { ComponentStatus, DotContentletDepths } from '@dotcms/dotcms-models';
import { withLocales } from '@dotcms/edit-content/feature/edit-content/store/features/locales.feature';

import { withContent } from './features/content.feature';
Expand Down Expand Up @@ -46,7 +46,7 @@ export const DotEditContentStore = signalStore(

// TODO: refactor this when we will use EditContent as sidebar
if (inode) {
store.initializeExistingContent(inode);
store.initializeExistingContent({ inode, depth: DotContentletDepths.TWO });
} else if (contentType) {
store.initializeNewContent(contentType);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
DotCMSContentType,
DotCMSWorkflow,
DotCMSWorkflowAction,
DotContentletDepth,
FeaturedFlags,
WorkflowStep
} from '@dotcms/dotcms-models';
Expand Down Expand Up @@ -233,12 +234,12 @@ export function withContent() {
* @returns {Observable<string>} An observable that emits the content's inode when initialization is complete
* @throws Will redirect to /c/content and show error if initialization fails
*/
initializeExistingContent: rxMethod<string>(
initializeExistingContent: rxMethod<{ inode: string; depth: DotContentletDepth }>(
nicobytes marked this conversation as resolved.
Show resolved Hide resolved
pipe(
switchMap((inode: string) => {
switchMap(({ inode, depth }) => {
patchState(store, { state: ComponentStatus.LOADING });

return dotEditContentService.getContentById(inode).pipe(
return dotEditContentService.getContentById({ id: inode, depth }).pipe(
switchMap((contentlet) => {
const { contentType } = contentlet;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,10 @@ describe('LocalesFeature', () => {
store.switchLocale(MOCK_LANGUAGES[1]);
tick();

expect(dotEditContentService.getContentById).toHaveBeenCalledWith('123', 2);
expect(dotEditContentService.getContentById).toHaveBeenCalledWith({
id: '123',
languageId: 2
});

expect(router.navigate).toHaveBeenCalledWith(['/content', '456'], {
replaceUrl: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,10 @@ export function withLocales() {
*/
if (locale.translated) {
return dotEditContentService
.getContentById(store.currentIdentifier(), locale.id)
.getContentById({
id: store.currentIdentifier(),
languageId: locale.id
})
.pipe(
tapResponse({
next: (contentlet) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export class DotFileFieldUploadService {
*/
getContentById(identifier: string) {
return this.#contentService
.getContentById(identifier)
.getContentById({ id: identifier })
.pipe(switchMap((contentlet) => this.#addContent(contentlet)));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,14 @@ export class DotEditContentRelationshipFieldComponent implements ControlValueAcc
allowSignalWrites: true
}
);

effect(() => {
if (this.onChange && this.onTouched) {
const value = this.store.formattedRelationship();
this.onChange(value);
this.onTouched();
}
});
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import { TestBed } from '@angular/core/testing';

import { ComponentStatus } from '@dotcms/dotcms-models';

import { RelationshipFieldStore } from './relationship-field.store';

import { RelationshipFieldItem } from '../models/relationship.models';

describe('RelationshipFieldStore', () => {
let store: InstanceType<typeof RelationshipFieldStore>;

const mockData: RelationshipFieldItem[] = [
{ id: '1', title: 'Content 1', language: '1', modDate: new Date().toISOString() },
{ id: '2', title: 'Content 2', language: '1', modDate: new Date().toISOString() },
{ id: '3', title: 'Content 3', language: '1', modDate: new Date().toISOString() }
];

beforeEach(() => {
TestBed.configureTestingModule({
providers: [RelationshipFieldStore]
});

store = TestBed.inject(RelationshipFieldStore);
});

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

describe('Initial State', () => {
it('should have correct initial state', () => {
expect(store.data()).toEqual([]);
expect(store.status()).toBe(ComponentStatus.INIT);
expect(store.selectionMode()).toBeNull();
expect(store.pagination()).toEqual({
offset: 0,
currentPage: 1,
rowsPerPage: 6
});
});
});

describe('State Management', () => {
describe('setData', () => {
it('should set data correctly', () => {
store.setData(mockData);
expect(store.data()).toEqual(mockData);
});
});

describe('setCardinality', () => {
it('should set single selection mode for ONE_TO_ONE relationship', () => {
store.setCardinality(2); // ONE_TO_ONE cardinality
expect(store.selectionMode()).toBe('single');
});

it('should set multiple selection mode for other relationship types', () => {
store.setCardinality(0); // ONE_TO_MANY cardinality
expect(store.selectionMode()).toBe('multiple');
});

it('should throw error for invalid cardinality', () => {
expect(() => store.setCardinality(999)).toThrow('Invalid relationship type');
});
});

describe('addData', () => {
it('should add new unique data to existing data', () => {
const initialData = [mockData[0]];
const newData = [mockData[1], mockData[2]];

store.setData(initialData);
store.addData(newData);

expect(store.data()).toEqual([...initialData, ...newData]);
});

it('should not add duplicate data', () => {
const initialData = [mockData[0]];
const newData = [mockData[0], mockData[1]];

store.setData(initialData);
store.addData(newData);

expect(store.data()).toEqual([mockData[0], mockData[1]]);
});
});

describe('pagination', () => {
it('should handle next page correctly', () => {
store.nextPage();
expect(store.pagination()).toEqual({
offset: 6,
currentPage: 2,
rowsPerPage: 6
});
});

it('should handle previous page correctly', () => {
store.nextPage();
store.previousPage();
expect(store.pagination()).toEqual({
offset: 0,
currentPage: 1,
rowsPerPage: 6
});
});
});
});

describe('Computed Properties', () => {
describe('totalPages', () => {
it('should compute total pages correctly', () => {
store.setData(mockData);
expect(store.totalPages()).toBe(1);
});

it('should handle empty data', () => {
expect(store.totalPages()).toBe(0);
});
});

describe('isDisabledCreateNewContent', () => {
it('should disable for single mode with one item', () => {
store.setCardinality(2); // ONE_TO_ONE
store.setData([mockData[0]]);
expect(store.isDisabledCreateNewContent()).toBe(true);
});

it('should not disable for single mode with no items', () => {
store.setCardinality(2); // ONE_TO_ONE
expect(store.isDisabledCreateNewContent()).toBe(false);
});

it('should not disable for multiple mode regardless of items', () => {
store.setCardinality(0); // ONE_TO_MANY
store.setData(mockData);
expect(store.isDisabledCreateNewContent()).toBe(false);
});
});

describe('formattedRelationship', () => {
it('should format relationship IDs correctly', () => {
store.setData(mockData);
expect(store.formattedRelationship()).toBe('1,2,3');
});

it('should handle empty data', () => {
expect(store.formattedRelationship()).toBe('');
});
});
});
});
Loading
Loading