Skip to content

Commit

Permalink
chore(edit-content): apply format to relationships (#31048)
Browse files Browse the repository at this point in the history
### Parent Issue

#31040 

### Proposed Changes

This pull request introduces a new enumeration `DotContentletDepths` to
manage the depth of contentlets and updates various components to
utilize this enumeration. The changes include updates to type
definitions, imports, and method calls to incorporate the new
`DotContentletDepths` enum.

### Introduction of `DotContentletDepths` Enumeration:

* Added `DotContentletDepths` enum to define the depth levels of
contentlets in `dot-contentlet.model.ts`.

### Updates to Import Statements:

* Updated import statements to include `DotContentletDepths` in
`dot-edit-content-form.component.spec.ts`, `edit-content.store.ts`,
`content.feature.ts`,
`dot-edit-content-relationship-field.component.ts`,
`dot-edit-content.service.spec.ts`, and `dot-edit-content.service.ts`.
[[1]](diffhunk://#diff-ba170b6ba64e637bbeea8b08f81a725fc7de75cfb94e910ab35148aa4c377ec5L29-R29)
[[2]](diffhunk://#diff-ed3e9ecca632a2f217989f35608b4eedd1576950bda10c74f8598e83a4703016L6-R6)
[[3]](diffhunk://#diff-45e4158706adbaf57599a885340534ed77401e3c69ea593dd9b377ffacc36958R32)
[[4]](diffhunk://#diff-479af3558b758325f127c8f7abe07cf9da59524f145b758f7c298e82a8e9cbadR15)
[[5]](diffhunk://#diff-ee2c6ce1c6a997f7b2b87106dbc020dba9d7f42fcdc29caebe547f6963e7caabL13-R13)

### Updates to Method Calls:

* Modified `initializeExistingContent` method calls to include the
`depth` parameter in `dot-edit-content-form.component.spec.ts` and
`edit-content.store.ts`.
[[1]](diffhunk://#diff-ba170b6ba64e637bbeea8b08f81a725fc7de75cfb94e910ab35148aa4c377ec5L125-R128)
[[2]](diffhunk://#diff-ba170b6ba64e637bbeea8b08f81a725fc7de75cfb94e910ab35148aa4c377ec5L203-R209)
[[3]](diffhunk://#diff-ba170b6ba64e637bbeea8b08f81a725fc7de75cfb94e910ab35148aa4c377ec5L296-R305)
[[4]](diffhunk://#diff-ba170b6ba64e637bbeea8b08f81a725fc7de75cfb94e910ab35148aa4c377ec5L310-R322)
[[5]](diffhunk://#diff-ba170b6ba64e637bbeea8b08f81a725fc7de75cfb94e910ab35148aa4c377ec5L323-R338)
[[6]](diffhunk://#diff-ba170b6ba64e637bbeea8b08f81a725fc7de75cfb94e910ab35148aa4c377ec5L337-R355)
[[7]](diffhunk://#diff-ed3e9ecca632a2f217989f35608b4eedd1576950bda10c74f8598e83a4703016L49-R49)

### Enhancements to Relationship Field Store:

* Added a new test file `relationship-field.store.spec.ts` to validate
the functionality of `RelationshipFieldStore`.
* Enhanced `relationship-field.store.ts` by adding computed properties
for pagination, disabling create new content button, and formatting
relationships. Removed unnecessary hooks and methods.
[[1]](diffhunk://#diff-a2c980da63c75c9dc2cd899475beab89cb79492b1dc0819574fc380e856e7ed1L5-L15)
[[2]](diffhunk://#diff-a2c980da63c75c9dc2cd899475beab89cb79492b1dc0819574fc380e856e7ed1R50-R58)
[[3]](diffhunk://#diff-a2c980da63c75c9dc2cd899475beab89cb79492b1dc0819574fc380e856e7ed1R68-R76)
[[4]](diffhunk://#diff-a2c980da63c75c9dc2cd899475beab89cb79492b1dc0819574fc380e856e7ed1L118-L124)
[[5]](diffhunk://#diff-a2c980da63c75c9dc2cd899475beab89cb79492b1dc0819574fc380e856e7ed1L151-L155)

### Service Method Updates:

* Updated `dot-edit-content.service.spec.ts` to include a test for
getting content by id and depth.

### Checklist
- [x] Tests
- [x] Translations
- [x] Security Implications Contemplated (add notes if applicable)
  • Loading branch information
nicobytes authored Jan 3, 2025
1 parent cbc0cfb commit e0b5efc
Show file tree
Hide file tree
Showing 12 changed files with 280 additions and 47 deletions.
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 @@ -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('');
});
});
});
});
Original file line number Diff line number Diff line change
@@ -1,18 +1,7 @@
import {
patchState,
signalStore,
withComputed,
withHooks,
withMethods,
withState
} from '@ngrx/signals';
import { rxMethod } from '@ngrx/signals/rxjs-interop';
import { pipe } from 'rxjs';
import { patchState, signalStore, withComputed, withMethods, withState } from '@ngrx/signals';

import { computed } from '@angular/core';

import { tap } from 'rxjs/operators';

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

import { RELATIONSHIP_OPTIONS } from '../dot-edit-content-relationship-field.constants';
Expand Down Expand Up @@ -52,7 +41,15 @@ export const RelationshipFieldStore = signalStore(
{ providedIn: 'root' },
withState(initialState),
withComputed((state) => ({
/**
* Computes the total number of pages based on the number of items and the rows per page.
* @returns {number} The total number of pages.
*/
totalPages: computed(() => Math.ceil(state.data().length / state.pagination().rowsPerPage)),
/**
* Checks if the create new content button is disabled based on the selection mode and the number of items.
* @returns {boolean} True if the button is disabled, false otherwise.
*/
isDisabledCreateNewContent: computed(() => {
const totalItems = state.data().length;
const selectionMode = state.selectionMode();
Expand All @@ -62,6 +59,15 @@ export const RelationshipFieldStore = signalStore(
}

return false;
}),
/**
* Formats the relationship field data into a string of IDs.
* @returns {string} A string of IDs separated by commas.
*/
formattedRelationship: computed(() => {
const data = state.data();

return data.map((item) => item.id).join(',');
})
})),
withMethods((store) => {
Expand Down Expand Up @@ -115,13 +121,6 @@ export const RelationshipFieldStore = signalStore(
data: store.data().filter((item) => item.id !== id)
});
},
/**
* Loads the data for the relationship field by fetching content from the service.
* It updates the state with the loaded data and sets the status to LOADED.
*/
loadData: rxMethod<void>(
pipe(tap(() => patchState(store, { status: ComponentStatus.LOADED })))
),
/**
* Advances the pagination to the next page and updates the state accordingly.
*/
Expand All @@ -147,10 +146,5 @@ export const RelationshipFieldStore = signalStore(
});
}
};
}),
withHooks({
onInit: (store) => {
store.loadData();
}
})
);
Loading

0 comments on commit e0b5efc

Please sign in to comment.