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

Added Select and Radio fields. Added basic tests on both components. … #26461

Merged

Conversation

KevinDavilaDotCMS
Copy link
Contributor

@KevinDavilaDotCMS KevinDavilaDotCMS commented Oct 18, 2023

πŸ€– Generated by Copilot at 22337ee

Summary

πŸ†•πŸ”§πŸš€

This pull request adds new components and interfaces to improve the edit content form feature. It adds dot-edit-content-select-field and dot-edit-content-radio-field components to handle select and radio fields, and EditContentFormData interface to define the data for the form. It also refactors and updates the existing components and tests to use the new interface and observables.

Sing, O Muse, of the mighty pull request
That reforged the dot-edit-content-form with skill and zest
And added new components for select and radio fields
Like the cunning Hephaestus who shapes the bronze and steel

Walkthrough

  • Add EditContentFormData interface to define the data for the edit content form (link)
  • Update edit-content-layout component to fetch and transform the data for the edit content form using DotEditContentService and map operator (link, link, link, link, link)
  • Update edit-content-form component to accept EditContentFormData as input and create form controls with initial values (link, link, link, link)
  • Update edit-content-field component to render select and radio fields using new components (link, link)
  • Add dot-edit-content-select-field component to render a select field using p-dropdown component and mapOptions function (link, link, link, link)
  • Add dot-edit-content-radio-field component to render a radio field using p-radioButton component and mapOptions function (link, link, link, link)
  • Update templates of edit-content-form and edit-content-layout components to use layout property of formData (link, link)
  • Update spec files of edit-content-form and edit-content-layout components to mock EditContentFormData and test form initialization and emission (link, link, link, link, link, link, link)
  • Import DotFieldRequiredDirective to edit-content-field module and use it in select and radio field templates to add required indicator to field label (link, link, link)

Additional Info

** any additional useful context or info **

Screenshots

Original Updated
** original screenshot ** ** updated screenshot **

…Changed formData structure and architecture
@KevinDavilaDotCMS KevinDavilaDotCMS linked an issue Oct 18, 2023 that may be closed by this pull request
@KevinDavilaDotCMS KevinDavilaDotCMS requested review from zJaaal, fmontes, oidacra and rjvelazco and removed request for zJaaal October 18, 2023 22:09
Comment on lines 9 to 15
<div class="field-checkbox" *ngFor="let option of options">
<p-radioButton
[inputId]="option.value"
[value]="option.value"
[formControlName]="field.variable"></p-radioButton>
<label class="ml-2" [for]="option.value">{{ option.label }}</label>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After you do the necessary changes, remember to make use of the :host

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can bind the class .field to the host.

Comment on lines 19 to 32
let newValue: string | number | boolean = value;
if (
dataType === DotEditContentFieldDataType.INTEGER ||
dataType === DotEditContentFieldDataType.FLOAT
) {
newValue = Number(value);
}

if (dataType === DotEditContentFieldDataType.BOOL) {
newValue = value === 'true';
}

return { label, value: newValue };
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we do all this process even if the label is the same as value?

@@ -211,7 +221,7 @@ describe('DotFormComponent', () => {
describe('saveContent', () => {
it('should emit the form value through the `formSubmit` event', () => {
const component = spectator.component;
component.formData = LAYOUT_MOCK;
component.formData = CONTENT_FORM_DATA_MOCK;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, that is the reason you need to add the line 225.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

@@ -0,0 +1,35 @@
import { DotEditContentFieldDataType } from '../enums/dot-edit-content-field.enum';

export const mapOptions = (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a test for this util to be sure it do the works?

@Component({
selector: 'dot-edit-content-select-field',
standalone: true,
imports: [CommonModule, DropdownModule, ReactiveFormsModule, DotFieldRequiredDirective],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add only the necessary from CommonModule


const createComponent = createComponentFactory({
component: DotEditContentSelectFieldComponent,
imports: [CommonModule, DropdownModule, ReactiveFormsModule, DotFieldRequiredDirective],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You dont need add imports. Is a Standalone component.

Comment on lines 9 to 15
<div class="field-checkbox" *ngFor="let option of options">
<p-radioButton
[inputId]="option.value"
[value]="option.value"
[formControlName]="field.variable"></p-radioButton>
<label class="ml-2" [for]="option.value">{{ option.label }}</label>
</div>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can bind the class .field to the host.

@@ -1,18 +1,36 @@
<ng-container [ngSwitch]="field.fieldType">
<div class="field" *ngSwitchCase="'Text'">
<div class="field">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can ged rid of this div and use the :host

];
spectator.setInput('field', RADIO_FIELD_BOOLEAN_MOCK);
spectator.detectChanges();
expect(spectator.component.options).toEqual(expectedList);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

expect(spectator.component.options).toEqual(expectedList);
});

it('should have a options array as radio with Boolean dataType', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you can group data types test under a describe and other as well.

];
spectator.setInput('field', SELECT_FIELD_TEXT_MOCK);
spectator.detectChanges();
expect(spectator.component.options).toEqual(expectedList);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests the DOM, for example here you need to test that you are passing the options and other attr to the <p-dropdown>

core-web/libs/edit-content/src/lib/utils/functions.util.ts Outdated Show resolved Hide resolved
@fmontes fmontes marked this pull request as ready for review October 21, 2023 02:05
<dot-edit-content-text-field
*ngSwitchCase="fieldTypes.TEXT"
[field]="field"
[attr.data-testId]="'field-' + field.variable"></dot-edit-content-text-field>
[attr.data-testId]="'field-' + field.variable" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

YES!!!

@@ -211,7 +221,7 @@ describe('DotFormComponent', () => {
describe('saveContent', () => {
it('should emit the form value through the `formSubmit` event', () => {
const component = spectator.component;
component.formData = LAYOUT_MOCK;
component.formData = CONTENT_FORM_DATA_MOCK;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

import { DotMessagePipe } from '@dotcms/ui';

import { EditContentFormData } from '../../interfaces/dot-edit-content-form.interface';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing this one.

@@ -31,7 +32,7 @@ import { DotEditContentFieldComponent } from '../dot-edit-content-field/dot-edit
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DotEditContentFormComponent implements OnInit {
@Input() formData: DotCMSContentTypeLayoutRow[] = [];
@Input() formData!: EditContentFormData;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

this.options = mapSelectableOptions(this.field.values || '', this.field.dataType);

if (this.formControl.value === null) {
this.formControl.setValue(this.options[0]?.value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any thoughts on this?

beforeEach(() => {
spectator = createComponent();
});
describe('DotEditContentRadioFieldComponent with Text DataType', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have DotEditContentRadioFieldComponent in the first describe, don't need to added here because in the results looks like redundant:

image

];
spectator.setInput('field', RADIO_FIELD_TEXT_MOCK);
spectator.detectChanges();
expect(spectator.component.options).toEqual(expectedList);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be testing the DOM like:

Suggested change
expect(spectator.component.options).toEqual(expectedList);
expect(spectator.query(Dropdown).options).toEqual(expectedList);

Comment on lines 67 to 68
expect(spanElement).toBeTruthy();
expect(spanElement.textContent).toEqual('Option 1');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
expect(spanElement).toBeTruthy();
expect(spanElement.textContent).toEqual('Option 1');
expect(spanElement.textContent).toEqual('Option 1');

Because only this will tests both.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should unify /interfaces and /enums in /models that's what we have been doing.

core-web/libs/edit-content/src/lib/utils/mock.ts Outdated Show resolved Hide resolved
Comment on lines 24 to 25
DotEditContentSelectFieldComponent,
DotEditContentRadioFieldComponent
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove from here and import and export these in the DotEditContentFieldsModule

return this.dotEditContentService.getContentTypeFormData(contentType);
return this.dotEditContentService
.getContentTypeFormData(contentType)
.pipe(map((res) => ({ values: { ...contentData }, layout: res })));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.pipe(map((res) => ({ values: { ...contentData }, layout: res })));
.pipe(map((res) => ({ contentlet: { ...contentData }, layout: res })));

According to our talk today.

@@ -0,0 +1,7 @@
<div class="field-checkbox" *ngFor="let option of options">
<p-radioButton
[inputId]="option.value"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't prevent the user to have the same value twice which result in this:

image

We can use field.variable which is unique and the index of the ngFor to create a unique id.

Comment on lines 47 to 53
expectedList.forEach((option) => {
expect(
spectator
.queryAll(RadioButton)
.find((radioOption) => radioOption.value === option.value)
).toBeTruthy();
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
expectedList.forEach((option) => {
expect(
spectator
.queryAll(RadioButton)
.find((radioOption) => radioOption.value === option.value)
).toBeTruthy();
});
expect(spectator.queryAll(RadioButton).map((radio) => radio.value)).toEqual([
'one',
'two'
]);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, we need to test the inputId and the label with the for (to test the semantic relationship) and the label innerText.

The same applies for all other cases but labels, labels can be tested just once.

Comment on lines 56 to 62
it('should render radio options', () => {
spectator.setInput('field', RADIO_FIELD_TEXT_MOCK);
spectator.detectComponentChanges();

const radioOptions = spectator.queryAll('p-radiobutton');
expect(radioOptions.length).toBe(2);
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we test the one above correctly, we might not need this one.

spectator.component.formControl.setValue('one');
spectator.detectComponentChanges();

expect(spectator.component.formControl.value).toEqual('one');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you are doing this

spectator.component.formControl.setValue('one');

It doesn't make sense do this:

expect(spectator.component.formControl.value).toEqual('one');

Comment on lines 80 to 81
const inputChecked = spectator.queryAll('div.p-radiobutton-checked');
expect(inputChecked.length).toBe(1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PrimeNG is a library that changes the CSS classes and HTML markup in their updates frequently.

Using a selector like div.p-radiobutton-checked for the test might breaks the tests in an update without us know it... we can check the checked property of the component instance.

Suggested change
const inputChecked = spectator.queryAll('div.p-radiobutton-checked');
expect(inputChecked.length).toBe(1);
const inputChecked = spectator.queryAll(RadioButton).map(radio => radio.checked);
expect(inputChecked.length).toBe(1);

return value;
};

export const createSingleSelectableFieldOptions = (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export const createSingleSelectableFieldOptions = (
export const getSingleSelectableFieldOptions = (

@dotcms-sonarqube
Copy link

@KevinDavilaDotCMS
Copy link
Contributor Author

For now we have an issue with the radio buttons.
If there are multiple options with the same "value", all those radio buttons will be selected.
It is a PrimeNg problem and its solution is pending investigation
More information => primefaces/primeng#9723

@fmontes fmontes merged commit b103c86 into master Oct 24, 2023
24 checks passed
@fmontes fmontes deleted the 26448-edit-content-render-radio-and-select-field-to-the-form branch October 24, 2023 20:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Edit Content: Render Radio and Select Field to the Form
4 participants