Skip to content

Commit

Permalink
feat(edit-content): implement relationship field data table with mock…
Browse files Browse the repository at this point in the history
… data (#30825)

### Parent Issue

#30523

### Proposed Changes

This pull request includes several changes to the `core-web` library,
focusing on improvements to the `dot-select-existing-content` component
and its associated files. The changes involve updates to the SCSS file,
HTML templates, TypeScript components, and the addition of new tests.
Here are the most important changes:

### SCSS Changes:
* Updated the primary tint color in `_colors.scss` from
`$color-palette-primary-200` to `$color-palette-primary-100`.

### HTML Template Changes:
* Added a condition to display the hint only if the field type is not
`RELATIONSHIP` in `dot-edit-content-field.component.html`.
* Removed the pagination HTML structure from `pagination.component.html`
and added a new structure with conditional layout based on
`$currentPageReportLayout`.

### TypeScript Component Changes:
* Added `output` import and defined `onSelectItems` signal to emit
selected items in `dot-select-existing-content.component.ts`.
[[1]](diffhunk://#diff-14d3d666b0a074fe26436013ebf4abe96e518e28e9799b85b393d14b82ac528bL2-R2)
[[2]](diffhunk://#diff-14d3d666b0a074fe26436013ebf4abe96e518e28e9799b85b393d14b82ac528bR93-R114)
* Replaced `Content` type with `RelationshipFieldItem` and updated the
store to use `tapResponse` for handling content loading in
`existing-content.store.ts`.
[[1]](diffhunk://#diff-1c1758cf3c19df1a6e75e613761290b504a1d26e8521af1492593e1825969b63R10-R22)
[[2]](diffhunk://#diff-1c1758cf3c19df1a6e75e613761290b504a1d26e8521af1492593e1825969b63L53-R76)

### Test Additions:
* Added a new test suite for `DotSelectExistingContentComponent` to
validate dialog visibility, selected items state, and apply button label
in `dot-select-existing-content.component.spec.ts`.

These changes enhance the functionality and maintainability of the
`dot-select-existing-content` component and its related parts.

### Checklist
- [ ] Tests
- [ ] Translations
- [ ] Security Implications Contemplated (add notes if applicable)
  • Loading branch information
nicobytes authored Dec 6, 2024
1 parent 8b77adb commit d19b399
Show file tree
Hide file tree
Showing 18 changed files with 593 additions and 128 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,5 @@ dotCMS/dependencies.gradle
!/examples/nextjs/.npmrc

.nx/
.cursorrules
.cursorignore
2 changes: 1 addition & 1 deletion core-web/libs/dotcms-scss/shared/_colors.scss
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ $color-palette-primary-op-90: var(--color-palette-primary-op-90);

$color-palette-primary-shade: $color-palette-primary-600;
$color-palette-primary: $color-palette-primary-500;
$color-palette-primary-tint: $color-palette-primary-200;
$color-palette-primary-tint: $color-palette-primary-100;

$color-palette-secondary-100: var(--color-palette-secondary-100);
$color-palette-secondary-200: var(--color-palette-secondary-200);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,6 @@
}
}
}
@if (field.hint) {
@if (field.hint && field.fieldType !== fieldTypes.RELATIONSHIP) {
<small [attr.data-testId]="'hint-' + field.variable">{{ field.hint }}</small>
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { EditorComponent } from '@tinymce/tinymce-angular';
import { MockComponent } from 'ng-mocks';
import { of } from 'rxjs';

import { HttpClientTestingModule } from '@angular/common/http/testing';
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { Provider, signal, Type } from '@angular/core';
import { ControlContainer, FormGroupDirective } from '@angular/forms';
import { By } from '@angular/platform-browser';
Expand Down Expand Up @@ -213,7 +214,7 @@ describe.each([...FIELDS_TO_BE_RENDER])('DotEditContentFieldComponent all fields
let spectator: Spectator<DotEditContentFieldComponent>;

const createComponent = createComponentFactory({
imports: [HttpClientTestingModule, ...(fieldTestBed?.imports || [])],
imports: [...(fieldTestBed?.imports || [])],
declarations: [...(fieldTestBed?.declarations || [])],
component: DotEditContentFieldComponent,
componentViewProviders: [
Expand All @@ -222,7 +223,13 @@ describe.each([...FIELDS_TO_BE_RENDER])('DotEditContentFieldComponent all fields
useValue: createFormGroupDirectiveMock()
}
],
providers: [FormGroupDirective, mockProvider(DotHttpErrorManagerService)]
providers: [
FormGroupDirective,
provideHttpClient(),
provideHttpClientTesting(),
...(fieldTestBed?.providers || []),
mockProvider(DotHttpErrorManagerService)
]
});

beforeEach(async () => {
Expand All @@ -242,11 +249,13 @@ describe.each([...FIELDS_TO_BE_RENDER])('DotEditContentFieldComponent all fields
expect(label?.textContent).toContain(fieldMock.name);
});

it('should render the hint if present', () => {
spectator.detectChanges();
const hint = spectator.query(byTestId(`hint-${fieldMock.variable}`));
expect(hint?.textContent).toContain(fieldMock.hint);
});
if (fieldMock.fieldType !== FIELD_TYPES.RELATIONSHIP) {
it('should render the hint if present', () => {
spectator.detectChanges();
const hint = spectator.query(byTestId(`hint-${fieldMock.variable}`));
expect(hint?.textContent).toContain(fieldMock.hint);
});
}

it('should render the correct field type', () => {
spectator.detectChanges();
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
[header]="'dot.file.relationship.dialog.select.existing.content' | dm"
[modal]="true"
[(visible)]="$visible"
(onHide)="emitSelectedItems()"
dataKey="id"
appendTo="body"
width="90%"
Expand All @@ -14,9 +15,9 @@
[value]="data"
selectionMode="multiple"
[(selection)]="$selectedItems"
[first]="pagination.offset"
[loading]="store.isLoading()"
[paginator]="true"
[first]="pagination.offset"
[rows]="pagination.rowsPerPage"
[globalFilterFields]="['title', 'step', 'description']"
styleClass="p-datatable-sm p-datatable-existing-content">
Expand All @@ -35,13 +36,12 @@
</p>
</div>
</div>
<div>
<dot-pagination
(nextPage)="store.nextPage()"
(previousPage)="store.previousPage()"
[totalPages]="store.totalPages()"
[currentPage]="pagination.currentPage" />
</div>
<dot-pagination
[currentPageReportLayout]="'left'"
(nextPage)="store.nextPage()"
(previousPage)="store.previousPage()"
[totalPages]="store.totalPages()"
[currentPage]="pagination.currentPage" />
</div>
</ng-template>
<ng-template pTemplate="header" styleClass="relative">
Expand Down Expand Up @@ -111,7 +111,10 @@
(onClick)="closeDialog()"
[text]="true"
severity="primary" />
<p-button [disabled]="$isApplyDisabled()" [label]="$applyLabel()" />
<p-button
[disabled]="$isApplyDisabled()"
(onClick)="closeDialog()"
[label]="$applyLabel()" />
</div>
</div>
</ng-template>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { Spectator, createComponentFactory } from '@ngneat/spectator/jest';

import { DotMessageService } from '@dotcms/data-access';
import { RelationshipFieldItem } from '@dotcms/edit-content/fields/dot-edit-content-relationship-field/models/relationship.models';
import { MockDotMessageService } from '@dotcms/utils-testing';

import { DotSelectExistingContentComponent } from './dot-select-existing-content.component';
import { ExistingContentStore } from './store/existing-content.store';

describe('DotSelectExistingContentComponent', () => {
let spectator: Spectator<DotSelectExistingContentComponent>;
let store: InstanceType<typeof ExistingContentStore>;

const mockRelationshipItem = (id: string): RelationshipFieldItem => ({
id,
title: `Test Content ${id}`,
language: '1',
state: {
label: 'Published',
styleClass: 'small-chip'
},
description: 'Test description',
step: 'Step 1',
lastUpdate: new Date().toISOString()
});

const messageServiceMock = new MockDotMessageService({
'dot.file.relationship.dialog.apply.one.entry': 'Apply 1 entry',
'dot.file.relationship.dialog.apply.entries': 'Apply {0} entries'
});

const createComponent = createComponentFactory({
component: DotSelectExistingContentComponent,
componentProviders: [ExistingContentStore],
providers: [{ provide: DotMessageService, useValue: messageServiceMock }],
detectChanges: false
});

beforeEach(() => {
spectator = createComponent();
store = spectator.inject(ExistingContentStore, true);
spectator.detectChanges();
});

it('should create', () => {
expect(spectator.component).toBeTruthy();
expect(store).toBeTruthy();
});

describe('Dialog Visibility', () => {
it('should set visibility to false when closeDialog is called', () => {
spectator.component.$visible.set(true);
spectator.component.closeDialog();
expect(spectator.component.$visible()).toBeFalsy();
});
});

describe('Selected Items State', () => {
it('should disable apply button when no items are selected', () => {
spectator.component.$selectedItems.set([]);
expect(spectator.component.$isApplyDisabled()).toBeTruthy();
});

it('should enable apply button when items are selected', () => {
const mockContent = [mockRelationshipItem('1')];
spectator.component.$selectedItems.set(mockContent);
expect(spectator.component.$isApplyDisabled()).toBeFalsy();
});
});

describe('Apply Button Label', () => {
it('should show singular label when one item is selected', () => {
const mockContent = [mockRelationshipItem('1')];
spectator.component.$selectedItems.set(mockContent);

const label = spectator.component.$applyLabel();
expect(label).toBe('Apply 1 entry');
});

it('should show plural label when multiple items are selected', () => {
const mockContent = [mockRelationshipItem('1'), mockRelationshipItem('2')];
spectator.component.$selectedItems.set(mockContent);

const label = spectator.component.$applyLabel();
expect(label).toBe('Apply 2 entries');
});
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DatePipe } from '@angular/common';
import { ChangeDetectionStrategy, Component, computed, inject, model } from '@angular/core';
import { ChangeDetectionStrategy, Component, computed, inject, model, output } from '@angular/core';

import { ButtonModule } from 'primeng/button';
import { DialogModule } from 'primeng/dialog';
Expand All @@ -14,9 +14,11 @@ import { TableModule } from 'primeng/table';
import { DotMessageService } from '@dotcms/data-access';
import { DotMessagePipe } from '@dotcms/ui';

import { PaginationComponent } from './components/pagination/pagination.component';
import { SearchComponent } from './components/search/search.compoment';
import { Content, ExistingContentStore } from './store/existing-content.store';
import { ExistingContentStore } from './store/existing-content.store';

import { RelationshipFieldItem } from '../../models/relationship.models';
import { PaginationComponent } from '../pagination/pagination.component';

@Component({
selector: 'dot-select-existing-content',
Expand Down Expand Up @@ -65,7 +67,7 @@ export class DotSelectExistingContentComponent {
* A signal that holds the selected items.
* It is used to store the selected content items.
*/
$selectedItems = model<Content[]>([]);
$selectedItems = model<RelationshipFieldItem[]>([]);

/**
* A computed signal that determines if the apply button is disabled.
Expand All @@ -88,11 +90,26 @@ export class DotSelectExistingContentComponent {
return this.#dotMessage.get(messageKey, selectedItems.length.toString());
});

/**
* A signal that sends the selected items when the dialog is closed.
* It is used to notify the parent component that the user has selected content items.
*/
onSelectItems = output<RelationshipFieldItem[]>();

/**
* A method that closes the existing content dialog.
* It sets the visibility signal to false, hiding the dialog.
*/
closeDialog() {
this.$visible.set(false);
}

/**
* Closes the existing content dialog and sends the selected items to the parent component.
* It sets the visibility signal to false, hiding the dialog, and emits the selected items
* through the "selectItems" output signal.
*/
emitSelectedItems() {
this.onSelectItems.emit(this.$selectedItems());
}
}
Loading

0 comments on commit d19b399

Please sign in to comment.