diff --git a/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/dot-edit-content-field.component.html b/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/dot-edit-content-field.component.html index b90a995d9bb6..a4a2559da91b 100644 --- a/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/dot-edit-content-field.component.html +++ b/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/dot-edit-content-field.component.html @@ -1,20 +1,18 @@ + -
- - - {{ - field.hint - }} -
+ +
+{{ field.hint }} diff --git a/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/dot-edit-content-field.component.scss b/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/dot-edit-content-field.component.scss index e69de29bb2d1..e17694394910 100644 --- a/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/dot-edit-content-field.component.scss +++ b/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/dot-edit-content-field.component.scss @@ -0,0 +1,4 @@ +:host { + display: block; + height: fit-content; +} diff --git a/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/dot-edit-content-field.component.spec.ts b/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/dot-edit-content-field.component.spec.ts index d00e9d2f1761..8a9f4badd07b 100644 --- a/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/dot-edit-content-field.component.spec.ts +++ b/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/dot-edit-content-field.component.spec.ts @@ -1,65 +1,32 @@ +import { describe } from '@jest/globals'; import { Spectator, byTestId, createComponentFactory } from '@ngneat/spectator'; -import { CommonModule } from '@angular/common'; -import { - ControlContainer, - FormControl, - FormGroup, - FormGroupDirective, - ReactiveFormsModule -} from '@angular/forms'; - -import { InputTextModule } from 'primeng/inputtext'; - -import { DotCMSContentTypeField } from '@dotcms/dotcms-models'; -import { DotFieldRequiredDirective } from '@dotcms/ui'; +import { ControlContainer, FormGroupDirective } from '@angular/forms'; +import { By } from '@angular/platform-browser'; import { DotEditContentFieldComponent } from './dot-edit-content-field.component'; +import { FIELD_TYPES, FIELD_TYPES_COMPONENTS } from './utils'; -export const FIELD_MOCK: DotCMSContentTypeField = { - clazz: 'com.dotcms.contenttype.model.field.ImmutableTextField', - contentTypeId: 'd46d6404125ac27e6ab68fad09266241', - dataType: 'TEXT', - fieldType: 'Text', - fieldTypeLabel: 'Text', - fieldVariables: [], - fixed: false, - iDate: 1696896882000, - id: 'c3b928bc2b59fc22c67022de4dd4b5c4', - indexed: false, - listed: false, - hint: 'A helper text', - modDate: 1696896882000, - name: 'testVariable', - readOnly: false, - required: false, - searchable: false, - sortOrder: 2, - unique: false, - variable: 'testVariable' -}; +import { FIELDS_MOCK, createFormGroupDirectiveMock } from '../../utils/mocks'; -const FORM_GROUP_MOCK = new FormGroup({ - testVariable: new FormControl('') +describe('FIELD_TYPES and FIELDS_MOCK', () => { + it('should be in sync', () => { + expect( + Object.values(FIELD_TYPES).every((fieldType) => + FIELDS_MOCK.find((f) => f.fieldType === fieldType) + ) + ).toBeTruthy(); + }); }); -const FORM_GROUP_DIRECTIVE_MOCK: FormGroupDirective = new FormGroupDirective([], []); -FORM_GROUP_DIRECTIVE_MOCK.form = FORM_GROUP_MOCK; -describe('DotFieldComponent', () => { +describe.each([...FIELDS_MOCK])('DotFieldComponent', (fieldMock) => { let spectator: Spectator; const createComponent = createComponentFactory({ component: DotEditContentFieldComponent, - imports: [ - DotEditContentFieldComponent, - CommonModule, - ReactiveFormsModule, - InputTextModule, - DotFieldRequiredDirective - ], componentViewProviders: [ { provide: ControlContainer, - useValue: FORM_GROUP_DIRECTIVE_MOCK + useValue: createFormGroupDirectiveMock() } ], providers: [FormGroupDirective] @@ -68,26 +35,31 @@ describe('DotFieldComponent', () => { beforeEach(async () => { spectator = createComponent({ props: { - field: FIELD_MOCK + field: fieldMock } }); }); it('should render the label', () => { spectator.detectChanges(); - const label = spectator.query(byTestId(`label-${FIELD_MOCK.variable}`)); - expect(label?.textContent).toContain(FIELD_MOCK.name); + const label = spectator.query(byTestId(`label-${fieldMock.variable}`)); + expect(label?.textContent).toContain(fieldMock.name); }); it('should render the hint', () => { spectator.detectChanges(); - const hint = spectator.query(byTestId(`hint-${FIELD_MOCK.variable}`)); - expect(hint?.textContent).toContain(FIELD_MOCK.hint); + const hint = spectator.query(byTestId(`hint-${fieldMock.variable}`)); + expect(hint?.textContent).toContain(fieldMock.hint); }); - it('should render the input', () => { + it('should render the correct field type', () => { spectator.detectChanges(); - const input = spectator.query(byTestId(`input-${FIELD_MOCK.variable}`)); - expect(input).toBeDefined(); + const field = spectator.debugElement.query( + By.css(`[data-testId="field-${fieldMock.variable}"]`) + ); + + expect( + field.componentInstance instanceof FIELD_TYPES_COMPONENTS[fieldMock.fieldType] + ).toBeTruthy(); }); }); diff --git a/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/dot-edit-content-field.component.ts b/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/dot-edit-content-field.component.ts index 06b930f69c15..98ce71c0fe2d 100644 --- a/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/dot-edit-content-field.component.ts +++ b/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/dot-edit-content-field.component.ts @@ -1,16 +1,25 @@ -import { CommonModule } from '@angular/common'; -import { ChangeDetectionStrategy, Component, Input, inject } from '@angular/core'; +import { NgIf, NgSwitch, NgSwitchCase } from '@angular/common'; +import { ChangeDetectionStrategy, Component, HostBinding, Input, inject } from '@angular/core'; import { ControlContainer, ReactiveFormsModule } from '@angular/forms'; -import { InputTextModule } from 'primeng/inputtext'; - import { DotCMSContentTypeField } from '@dotcms/dotcms-models'; import { DotFieldRequiredDirective } from '@dotcms/ui'; +import { FIELD_TYPES } from './utils'; + +import { DotEditContentFieldsModule } from '../../fields/dot-edit-content-fields.module'; + @Component({ selector: 'dot-edit-content-field', standalone: true, - imports: [CommonModule, ReactiveFormsModule, InputTextModule, DotFieldRequiredDirective], + imports: [ + NgSwitch, + NgSwitchCase, + NgIf, + ReactiveFormsModule, + DotEditContentFieldsModule, + DotFieldRequiredDirective + ], templateUrl: './dot-edit-content-field.component.html', styleUrls: ['./dot-edit-content-field.component.scss'], viewProviders: [ @@ -22,5 +31,7 @@ import { DotFieldRequiredDirective } from '@dotcms/ui'; changeDetection: ChangeDetectionStrategy.OnPush }) export class DotEditContentFieldComponent { + @HostBinding('class') class = 'field'; @Input() field!: DotCMSContentTypeField; + readonly fieldTypes = FIELD_TYPES; } diff --git a/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/utils.ts b/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/utils.ts new file mode 100644 index 000000000000..b913b01741e3 --- /dev/null +++ b/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/utils.ts @@ -0,0 +1,17 @@ +import { Type } from '@angular/core'; + +import { DotEditContentTextAreaComponent } from '../../fields/dot-edit-content-text-area/dot-edit-content-text-area.component'; +import { DotEditContentTextFieldComponent } from '../../fields/dot-edit-content-text-field/dot-edit-content-text-field.component'; + +// Map to match the field type to component selector +export enum FIELD_TYPES { + TEXT = 'Text', + TEXTAREA = 'Textarea' +} + +// This holds the mapping between the field type and the component that should be used to render it. +export const FIELD_TYPES_COMPONENTS: Record> = { + // We had to use unknown because components have different types. + [FIELD_TYPES.TEXT]: DotEditContentTextFieldComponent, + [FIELD_TYPES.TEXTAREA]: DotEditContentTextAreaComponent +}; diff --git a/core-web/libs/edit-content/src/lib/components/dot-edit-content-form/dot-edit-content-form.component.html b/core-web/libs/edit-content/src/lib/components/dot-edit-content-form/dot-edit-content-form.component.html index 02ed3c823e6b..424615f1f9b2 100644 --- a/core-web/libs/edit-content/src/lib/components/dot-edit-content-form/dot-edit-content-form.component.html +++ b/core-web/libs/edit-content/src/lib/components/dot-edit-content-form/dot-edit-content-form.component.html @@ -1,4 +1,4 @@ -
+
diff --git a/core-web/libs/edit-content/src/lib/components/dot-edit-content-form/dot-edit-content-form.component.scss b/core-web/libs/edit-content/src/lib/components/dot-edit-content-form/dot-edit-content-form.component.scss index f83f561bea28..1ea595120f12 100644 --- a/core-web/libs/edit-content/src/lib/components/dot-edit-content-form/dot-edit-content-form.component.scss +++ b/core-web/libs/edit-content/src/lib/components/dot-edit-content-form/dot-edit-content-form.component.scss @@ -5,6 +5,13 @@ grid-template-columns: 1fr 16rem; gap: $spacing-4; padding: $spacing-4; + padding-bottom: 0; + height: 100%; +} + +.edit-content-form { + overflow: auto; + padding-right: $spacing-4; } .row { @@ -12,9 +19,10 @@ grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: $spacing-2; +} - .column { - display: grid; - min-height: $spacing-7; - } +.column { + display: flex; + flex-direction: column; + gap: $spacing-3; } diff --git a/core-web/libs/edit-content/src/lib/components/dot-edit-content-form/dot-edit-content-form.component.spec.ts b/core-web/libs/edit-content/src/lib/components/dot-edit-content-form/dot-edit-content-form.component.spec.ts index 918bff75098c..eaf08ad665f6 100644 --- a/core-web/libs/edit-content/src/lib/components/dot-edit-content-form/dot-edit-content-form.component.spec.ts +++ b/core-web/libs/edit-content/src/lib/components/dot-edit-content-form/dot-edit-content-form.component.spec.ts @@ -6,158 +6,13 @@ import { ReactiveFormsModule } from '@angular/forms'; import { ButtonModule } from 'primeng/button'; import { DotMessageService } from '@dotcms/data-access'; -import { DotCMSContentTypeLayoutRow } from '@dotcms/dotcms-models'; import { DotMessagePipe } from '@dotcms/ui'; import { MockDotMessageService } from '@dotcms/utils-testing'; import { DotEditContentFormComponent } from './dot-edit-content-form.component'; +import { FIELDS_MOCK, FIELD_MOCK, LAYOUT_MOCK } from '../../utils/mocks'; import { DotEditContentFieldComponent } from '../dot-edit-content-field/dot-edit-content-field.component'; -import { FIELD_MOCK } from '../dot-edit-content-field/dot-edit-content-field.component.spec'; - -export const LAYOUT_MOCK: DotCMSContentTypeLayoutRow[] = [ - { - divider: { - clazz: 'com.dotcms.contenttype.model.field.ImmutableRowField', - contentTypeId: 'd46d6404125ac27e6ab68fad09266241', - dataType: 'SYSTEM', - fieldType: 'Row', - fieldTypeLabel: 'Row', - fieldVariables: [], - fixed: false, - iDate: 1697051073000, - id: 'a31ea895f80eb0a3754e4a2292e09a52', - indexed: false, - listed: false, - modDate: 1697051077000, - name: 'fields-0', - readOnly: false, - required: false, - searchable: false, - sortOrder: 0, - unique: false, - variable: 'fields0' - }, - columns: [ - { - columnDivider: { - clazz: 'com.dotcms.contenttype.model.field.ImmutableColumnField', - contentTypeId: 'd46d6404125ac27e6ab68fad09266241', - dataType: 'SYSTEM', - fieldType: 'Column', - fieldTypeLabel: 'Column', - fieldVariables: [], - fixed: false, - iDate: 1697051073000, - id: 'd4c32b4b9fb5b11c58c245d4a02bef47', - indexed: false, - listed: false, - modDate: 1697051077000, - name: 'fields-1', - readOnly: false, - required: false, - searchable: false, - sortOrder: 1, - unique: false, - variable: 'fields1' - }, - fields: [ - { - clazz: 'com.dotcms.contenttype.model.field.ImmutableTextField', - contentTypeId: 'd46d6404125ac27e6ab68fad09266241', - dataType: 'TEXT', - defaultValue: 'Placeholder', - fieldType: 'Text', - fieldTypeLabel: 'Text', - fieldVariables: [], - fixed: false, - hint: 'A hint Text', - iDate: 1697051093000, - id: '1d1505a4569681b923769acb785fd093', - indexed: false, - listed: false, - modDate: 1697051093000, - name: 'name1', - readOnly: false, - required: true, - searchable: false, - sortOrder: 2, - unique: false, - variable: 'name1' - }, - { - clazz: 'com.dotcms.contenttype.model.field.ImmutableTextField', - contentTypeId: 'd46d6404125ac27e6ab68fad09266241', - dataType: 'TEXT', - fieldType: 'Text', - fieldTypeLabel: 'Text', - fieldVariables: [], - fixed: false, - iDate: 1697051107000, - id: 'fc776c45044f2d043f5e98eaae36c9ff', - indexed: false, - listed: false, - modDate: 1697051107000, - name: 'text2', - readOnly: false, - required: true, - searchable: false, - sortOrder: 3, - unique: false, - variable: 'text2' - } - ] - }, - { - columnDivider: { - clazz: 'com.dotcms.contenttype.model.field.ImmutableColumnField', - contentTypeId: 'd46d6404125ac27e6ab68fad09266241', - dataType: 'SYSTEM', - fieldType: 'Column', - fieldTypeLabel: 'Column', - fieldVariables: [], - fixed: false, - iDate: 1697051077000, - id: '848fc78a11e7290efad66eb39333ae2b', - indexed: false, - listed: false, - modDate: 1697051107000, - name: 'fields-2', - readOnly: false, - required: false, - searchable: false, - sortOrder: 4, - unique: false, - variable: 'fields2' - }, - fields: [ - { - clazz: 'com.dotcms.contenttype.model.field.ImmutableTextField', - contentTypeId: 'd46d6404125ac27e6ab68fad09266241', - dataType: 'TEXT', - fieldType: 'Text', - fieldTypeLabel: 'Text', - fieldVariables: [], - fixed: false, - hint: 'A hint text2', - iDate: 1697051118000, - id: '1f6765de8d4ad069ff308bfca56b9255', - indexed: false, - listed: false, - modDate: 1697051118000, - name: 'text3', - readOnly: false, - required: false, - searchable: false, - sortOrder: 5, - unique: false, - variable: 'text3' - } - ] - } - ] - } -]; describe('DotFormComponent', () => { let spectator: Spectator; @@ -206,6 +61,11 @@ describe('DotFormComponent', () => { expect(formControl).toBeDefined(); expect(formControl.validator).toBeDefined(); }); + + it('should have a default value if is defined', () => { + const formControl = spectator.component.initializeFormControl(FIELDS_MOCK[1]); + expect(formControl.value).toEqual(FIELDS_MOCK[1].defaultValue); + }); }); describe('saveContent', () => { diff --git a/core-web/libs/edit-content/src/lib/components/dot-edit-content-form/dot-edit-content-form.component.ts b/core-web/libs/edit-content/src/lib/components/dot-edit-content-form/dot-edit-content-form.component.ts index 95a6424a67a8..85f631628f47 100644 --- a/core-web/libs/edit-content/src/lib/components/dot-edit-content-form/dot-edit-content-form.component.ts +++ b/core-web/libs/edit-content/src/lib/components/dot-edit-content-form/dot-edit-content-form.component.ts @@ -76,7 +76,13 @@ export class DotEditContentFormComponent implements OnInit { } } - return this.fb.control(null, { validators }); + return this.fb.control( + { + value: field.defaultValue || '', + disabled: field.readOnly + }, + { validators } + ); } /** diff --git a/core-web/libs/edit-content/src/lib/feature/edit-content/edit-content.layout.component.spec.ts b/core-web/libs/edit-content/src/lib/feature/edit-content/edit-content.layout.component.spec.ts index 09cc48d45467..a13a065df2d1 100644 --- a/core-web/libs/edit-content/src/lib/feature/edit-content/edit-content.layout.component.spec.ts +++ b/core-web/libs/edit-content/src/lib/feature/edit-content/edit-content.layout.component.spec.ts @@ -4,179 +4,10 @@ import { of } from 'rxjs'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { ActivatedRoute } from '@angular/router'; -import { DotCMSContentType } from '@dotcms/dotcms-models'; - import { EditContentLayoutComponent } from './edit-content.layout.component'; -import { LAYOUT_MOCK } from '../../components/dot-edit-content-form/dot-edit-content-form.component.spec'; import { DotEditContentService } from '../../services/dot-edit-content.service'; - -export const CONTENT_TYPE_MOCK: DotCMSContentType = { - baseType: 'CONTENT', - clazz: 'com.dotcms.contenttype.model.type.ImmutableSimpleContentType', - defaultType: false, - fields: [ - { - clazz: 'com.dotcms.contenttype.model.field.ImmutableRowField', - contentTypeId: 'd46d6404125ac27e6ab68fad09266241', - dataType: 'SYSTEM', - fieldType: 'Row', - fieldTypeLabel: 'Row', - fieldVariables: [], - fixed: false, - iDate: 1697051073000, - id: 'a31ea895f80eb0a3754e4a2292e09a52', - indexed: false, - listed: false, - modDate: 1697051077000, - name: 'fields-0', - readOnly: false, - required: false, - searchable: false, - sortOrder: 0, - unique: false, - variable: 'fields0' - }, - { - clazz: 'com.dotcms.contenttype.model.field.ImmutableColumnField', - contentTypeId: 'd46d6404125ac27e6ab68fad09266241', - dataType: 'SYSTEM', - fieldType: 'Column', - fieldTypeLabel: 'Column', - fieldVariables: [], - fixed: false, - iDate: 1697051073000, - id: 'd4c32b4b9fb5b11c58c245d4a02bef47', - indexed: false, - listed: false, - modDate: 1697051077000, - name: 'fields-1', - readOnly: false, - required: false, - searchable: false, - sortOrder: 1, - unique: false, - variable: 'fields1' - }, - { - clazz: 'com.dotcms.contenttype.model.field.ImmutableTextField', - contentTypeId: 'd46d6404125ac27e6ab68fad09266241', - dataType: 'TEXT', - defaultValue: 'Placeholder', - fieldType: 'Text', - fieldTypeLabel: 'Text', - fieldVariables: [], - fixed: false, - hint: 'A hint Text', - iDate: 1697051093000, - id: '1d1505a4569681b923769acb785fd093', - indexed: false, - listed: false, - modDate: 1697051093000, - name: 'name1', - readOnly: false, - required: true, - searchable: false, - sortOrder: 2, - unique: false, - variable: 'name1' - }, - { - clazz: 'com.dotcms.contenttype.model.field.ImmutableTextField', - contentTypeId: 'd46d6404125ac27e6ab68fad09266241', - dataType: 'TEXT', - fieldType: 'Text', - fieldTypeLabel: 'Text', - fieldVariables: [], - fixed: false, - iDate: 1697051107000, - id: 'fc776c45044f2d043f5e98eaae36c9ff', - indexed: false, - listed: false, - modDate: 1697051107000, - name: 'text2', - readOnly: false, - required: true, - searchable: false, - sortOrder: 3, - unique: false, - variable: 'text2' - }, - { - clazz: 'com.dotcms.contenttype.model.field.ImmutableColumnField', - contentTypeId: 'd46d6404125ac27e6ab68fad09266241', - dataType: 'SYSTEM', - fieldType: 'Column', - fieldTypeLabel: 'Column', - fieldVariables: [], - fixed: false, - iDate: 1697051077000, - id: '848fc78a11e7290efad66eb39333ae2b', - indexed: false, - listed: false, - modDate: 1697051107000, - name: 'fields-2', - readOnly: false, - required: false, - searchable: false, - sortOrder: 4, - unique: false, - variable: 'fields2' - }, - { - clazz: 'com.dotcms.contenttype.model.field.ImmutableTextField', - contentTypeId: 'd46d6404125ac27e6ab68fad09266241', - dataType: 'TEXT', - fieldType: 'Text', - fieldTypeLabel: 'Text', - fieldVariables: [], - fixed: false, - hint: 'A hint text2', - iDate: 1697051118000, - id: '1f6765de8d4ad069ff308bfca56b9255', - indexed: false, - listed: false, - modDate: 1697051118000, - name: 'text3', - readOnly: false, - required: false, - searchable: false, - sortOrder: 5, - unique: false, - variable: 'text3' - } - ], - fixed: false, - folder: 'SYSTEM_FOLDER', - host: '48190c8c-42c4-46af-8d1a-0cd5db894797', - iDate: 1697051073000, - icon: 'event_note', - id: 'd46d6404125ac27e6ab68fad09266241', - layout: LAYOUT_MOCK, - modDate: 1697051118000, - multilingualable: false, - name: 'Test', - contentType: 'Test', - system: false, - systemActionMappings: {}, - variable: 'Test', - versionable: true, - workflows: [ - { - archived: false, - creationDate: new Date(1697047303976), - defaultScheme: false, - description: '', - entryActionId: null, - id: 'd61a59e1-a49c-46f2-a929-db2b4bfa88b2', - mandatory: false, - modDate: new Date(1697047292887), - name: 'System Workflow', - system: true - } - ], - nEntries: 0 -}; +import { CONTENT_TYPE_MOCK, LAYOUT_MOCK } from '../../utils/mocks'; const createEditContentLayoutComponent = (params: { contentType?: string; id?: string }) => { return createComponentFactory({ diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-fields.module.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-fields.module.ts new file mode 100644 index 000000000000..15571c533f61 --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-fields.module.ts @@ -0,0 +1,11 @@ +import { NgModule } from '@angular/core'; + +import { DotEditContentTextAreaComponent } from './dot-edit-content-text-area/dot-edit-content-text-area.component'; +import { DotEditContentTextFieldComponent } from './dot-edit-content-text-field/dot-edit-content-text-field.component'; + +@NgModule({ + declarations: [], + imports: [DotEditContentTextAreaComponent, DotEditContentTextFieldComponent], + exports: [DotEditContentTextAreaComponent, DotEditContentTextFieldComponent] +}) +export class DotEditContentFieldsModule {} diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-text-area/dot-edit-content-text-area.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-text-area/dot-edit-content-text-area.component.html new file mode 100644 index 000000000000..95c561ebb364 --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-text-area/dot-edit-content-text-area.component.html @@ -0,0 +1,9 @@ + diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-text-area/dot-edit-content-text-area.component.scss b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-text-area/dot-edit-content-text-area.component.scss new file mode 100644 index 000000000000..a8412ce9f6b3 --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-text-area/dot-edit-content-text-area.component.scss @@ -0,0 +1,4 @@ +:host { + height: fit-content; + display: block; +} diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-text-area/dot-edit-content-text-area.component.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-text-area/dot-edit-content-text-area.component.spec.ts new file mode 100644 index 000000000000..073514947d0e --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-text-area/dot-edit-content-text-area.component.spec.ts @@ -0,0 +1,62 @@ +import { expect, it, test } from '@jest/globals'; +import { Spectator, byTestId, createComponentFactory } from '@ngneat/spectator'; + +import { CommonModule } from '@angular/common'; +import { ReactiveFormsModule, ControlContainer, FormGroupDirective } from '@angular/forms'; + +import { InputTextareaModule } from 'primeng/inputtextarea'; + +import { DotFieldRequiredDirective } from '@dotcms/ui'; + +import { DotEditContentTextAreaComponent } from './dot-edit-content-text-area.component'; + +import { createFormGroupDirectiveMock, TEXT_AREA_FIELD_MOCK } from '../../utils/mocks'; + +describe('DotEditContentTextAreaComponent', () => { + let spectator: Spectator; + let textArea: Element; + + const createComponent = createComponentFactory({ + component: DotEditContentTextAreaComponent, + imports: [ + CommonModule, + ReactiveFormsModule, + InputTextareaModule, + DotFieldRequiredDirective + ], + componentViewProviders: [ + { + provide: ControlContainer, + useValue: createFormGroupDirectiveMock() + } + ], + providers: [FormGroupDirective] + }); + + beforeEach(() => { + spectator = createComponent({ + props: { + field: TEXT_AREA_FIELD_MOCK + } + }); + + textArea = spectator.query(byTestId(TEXT_AREA_FIELD_MOCK.variable)); + }); + + test.each([ + { + variable: TEXT_AREA_FIELD_MOCK.variable, + attribute: 'id' + }, + { + variable: TEXT_AREA_FIELD_MOCK.variable, + attribute: 'ng-reflect-name' + } + ])('should have the $variable as $attribute', ({ variable, attribute }) => { + expect(textArea.getAttribute(attribute)).toBe(variable); + }); + + it('should have min height as 9.375rem and resize as vertical', () => { + expect(textArea.getAttribute('style')).toBe('min-height: 9.375rem; resize: vertical;'); + }); +}); diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-text-area/dot-edit-content-text-area.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-text-area/dot-edit-content-text-area.component.ts new file mode 100644 index 000000000000..120712267619 --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-text-area/dot-edit-content-text-area.component.ts @@ -0,0 +1,24 @@ +import { ChangeDetectionStrategy, Component, Input, inject } from '@angular/core'; +import { ControlContainer, ReactiveFormsModule } from '@angular/forms'; + +import { InputTextareaModule } from 'primeng/inputtextarea'; + +import { DotCMSContentTypeField } from '@dotcms/dotcms-models'; + +@Component({ + selector: 'dot-edit-content-text-area', + templateUrl: './dot-edit-content-text-area.component.html', + styleUrls: ['./dot-edit-content-text-area.component.scss'], + standalone: true, + imports: [InputTextareaModule, ReactiveFormsModule], + changeDetection: ChangeDetectionStrategy.OnPush, + viewProviders: [ + { + provide: ControlContainer, + useFactory: () => inject(ControlContainer, { skipSelf: true }) + } + ] +}) +export class DotEditContentTextAreaComponent { + @Input() field: DotCMSContentTypeField; +} diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-text-field/dot-edit-content-text-field.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-text-field/dot-edit-content-text-field.component.html new file mode 100644 index 000000000000..dbdf757f4798 --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-text-field/dot-edit-content-text-field.component.html @@ -0,0 +1,8 @@ + diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-text-field/dot-edit-content-text-field.component.scss b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-text-field/dot-edit-content-text-field.component.scss new file mode 100644 index 000000000000..a8412ce9f6b3 --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-text-field/dot-edit-content-text-field.component.scss @@ -0,0 +1,4 @@ +:host { + height: fit-content; + display: block; +} diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-text-field/dot-edit-content-text-field.component.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-text-field/dot-edit-content-text-field.component.spec.ts new file mode 100644 index 000000000000..07cdf5b7bc4e --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-text-field/dot-edit-content-text-field.component.spec.ts @@ -0,0 +1,95 @@ +import { test, describe } from '@jest/globals'; +import { Spectator, byTestId, createComponentFactory } from '@ngneat/spectator'; + +import { CommonModule } from '@angular/common'; +import { ReactiveFormsModule, ControlContainer, FormGroupDirective } from '@angular/forms'; + +import { InputTextModule } from 'primeng/inputtext'; + +import { DotFieldRequiredDirective } from '@dotcms/ui'; + +import { DotEditContentTextFieldComponent } from './dot-edit-content-text-field.component'; +import { INPUT_TEXT_OPTIONS, INPUT_TYPE } from './utils'; + +import { TEXT_FIELD_MOCK, createFormGroupDirectiveMock } from '../../utils/mocks'; + +describe('DotEditContentTextFieldComponent', () => { + let spectator: Spectator; + let textInput: Element; + + const createComponent = createComponentFactory({ + component: DotEditContentTextFieldComponent, + imports: [CommonModule, ReactiveFormsModule, InputTextModule, DotFieldRequiredDirective], + componentViewProviders: [ + { + provide: ControlContainer, + useValue: createFormGroupDirectiveMock() + } + ], + providers: [FormGroupDirective] + }); + beforeEach(() => { + spectator = createComponent({ + props: { + field: TEXT_FIELD_MOCK + } + }); + + textInput = spectator.query(byTestId(TEXT_FIELD_MOCK.variable)); + }); + + test.each([ + { + variable: TEXT_FIELD_MOCK.variable, + attribute: 'id' + }, + { + variable: TEXT_FIELD_MOCK.variable, + attribute: 'ng-reflect-name' + } + ])('should have the $variable as $attribute', ({ variable, attribute }) => { + expect(textInput.getAttribute(attribute)).toBe(variable); + }); + + describe.each([ + { + dataType: INPUT_TYPE.TEXT + }, + { + dataType: INPUT_TYPE.INTEGER + }, + { + dataType: INPUT_TYPE.FLOAT + } + ])('with dataType as $dataType', ({ dataType }) => { + const options = INPUT_TEXT_OPTIONS[dataType]; + + beforeEach(() => { + spectator = createComponent({ + props: { + field: { ...TEXT_FIELD_MOCK, dataType } + } + }); + + textInput = spectator.query(byTestId(TEXT_FIELD_MOCK.variable)); + }); + + it('should have the type as defined in the options', () => { + expect(textInput.getAttribute('type')).toBe(options.type); + }); + + it('should have the inputMode as defined in the options', () => { + expect(textInput.getAttribute('inputmode')).toBe(options.inputMode); + }); + + it('should have the step as defined in the options', () => { + if (options.step === undefined) { + expect(textInput.getAttribute('step')).toBeNull(); + + return; + } + + expect(textInput.getAttribute('step')).toBe(options.step.toString()); + }); + }); +}); diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-text-field/dot-edit-content-text-field.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-text-field/dot-edit-content-text-field.component.ts new file mode 100644 index 000000000000..5ec0ff2d0274 --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-text-field/dot-edit-content-text-field.component.ts @@ -0,0 +1,28 @@ +import { ChangeDetectionStrategy, Component, Input, inject } from '@angular/core'; +import { ControlContainer, ReactiveFormsModule } from '@angular/forms'; + +import { InputTextModule } from 'primeng/inputtext'; + +import { DotCMSContentTypeField } from '@dotcms/dotcms-models'; + +import { INPUT_TEXT_OPTIONS } from './utils'; + +@Component({ + selector: 'dot-edit-content-text-field', + templateUrl: './dot-edit-content-text-field.component.html', + styleUrls: ['./dot-edit-content-text-field.component.scss'], + standalone: true, + imports: [ReactiveFormsModule, InputTextModule], + changeDetection: ChangeDetectionStrategy.OnPush, + viewProviders: [ + { + provide: ControlContainer, + useFactory: () => inject(ControlContainer, { skipSelf: true }) + } + ] +}) +export class DotEditContentTextFieldComponent { + @Input() field!: DotCMSContentTypeField; + + readonly inputTextOptions = INPUT_TEXT_OPTIONS; +} diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-text-field/utils.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-text-field/utils.ts new file mode 100644 index 000000000000..49efb5ecffbd --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-text-field/utils.ts @@ -0,0 +1,31 @@ +// Input type that you can select when creating the field +export enum INPUT_TYPE { + TEXT = 'TEXT', + INTEGER = 'INTEGER', + FLOAT = 'FLOAT' +} + +// This is to hold the options for the input type +export interface InputTextOptions { + type: string; + inputMode: string; + step?: number; +} + +// This is to hold the options for the input type +export const INPUT_TEXT_OPTIONS: Record = { + TEXT: { + type: 'text', + inputMode: 'text' + }, + INTEGER: { + type: 'number', + inputMode: 'numeric', + step: 1 + }, + FLOAT: { + type: 'number', + inputMode: 'decimal', + step: 0.1 + } +}; diff --git a/core-web/libs/edit-content/src/lib/services/dot-edit-content.service.spec.ts b/core-web/libs/edit-content/src/lib/services/dot-edit-content.service.spec.ts index f92b3d85f140..03b8373da6a4 100644 --- a/core-web/libs/edit-content/src/lib/services/dot-edit-content.service.spec.ts +++ b/core-web/libs/edit-content/src/lib/services/dot-edit-content.service.spec.ts @@ -11,7 +11,7 @@ import { DotContentTypeService, DotWorkflowActionsFireService } from '@dotcms/da import { DotEditContentService } from './dot-edit-content.service'; -import { CONTENT_TYPE_MOCK } from '../feature/edit-content/edit-content.layout.component.spec'; +import { CONTENT_TYPE_MOCK } from '../utils/mocks'; const API_ENDPOINT = '/api/v1/content'; diff --git a/core-web/libs/edit-content/src/lib/utils/mocks.ts b/core-web/libs/edit-content/src/lib/utils/mocks.ts new file mode 100644 index 000000000000..0d33a0bb1558 --- /dev/null +++ b/core-web/libs/edit-content/src/lib/utils/mocks.ts @@ -0,0 +1,399 @@ +import { + FormGroup, + FormControl, + FormGroupDirective, + AsyncValidator, + ValidatorFn, + Validator +} from '@angular/forms'; + +import { + DotCMSContentType, + DotCMSContentTypeField, + DotCMSContentTypeLayoutRow +} from '@dotcms/dotcms-models'; + +export const TEXT_FIELD_MOCK: DotCMSContentTypeField = { + clazz: 'com.dotcms.contenttype.model.field.ImmutableTextField', + contentTypeId: 'd46d6404125ac27e6ab68fad09266241', + dataType: 'TEXT', + fieldType: 'Text', + fieldTypeLabel: 'Text', + fieldVariables: [], + fixed: false, + iDate: 1696896882000, + id: 'c3b928bc2b59fc22c67022de4dd4b5c4', + indexed: false, + listed: false, + hint: 'A helper text', + modDate: 1696896882000, + name: 'testVariable', + readOnly: false, + required: false, + searchable: false, + sortOrder: 2, + unique: false, + variable: 'testVariable' +}; + +export const TEXT_AREA_FIELD_MOCK: DotCMSContentTypeField = { + clazz: 'com.dotcms.contenttype.model.field.ImmutableTextAreaField', + contentTypeId: '61226fd915b7f025da020fc1f5856ab7', + dataType: 'LONG_TEXT', + defaultValue: 'Some value', + fieldType: 'Textarea', + fieldTypeLabel: 'Textarea', + fieldVariables: [], + fixed: false, + hint: 'Some hint', + iDate: 1697553818000, + id: '950c7ddbbe59996386330316a32cccc4', + indexed: false, + listed: false, + modDate: 1697554437000, + name: 'some text area', + readOnly: false, + required: true, + searchable: false, + sortOrder: 2, + unique: false, + variable: 'someTextArea' +}; + +export const FIELDS_MOCK: DotCMSContentTypeField[] = [TEXT_FIELD_MOCK, TEXT_AREA_FIELD_MOCK]; + +export const FIELD_MOCK: DotCMSContentTypeField = TEXT_FIELD_MOCK; + +// This creates a mock FormGroup from an array of fielda +export const createFormControlObjectMock = (fields = FIELDS_MOCK) => { + return fields.reduce((acc, field) => { + acc[field.variable] = new FormControl(''); + + return acc; + }, {}); +}; + +export const FORM_GROUP_MOCK = new FormGroup(createFormControlObjectMock()); + +// Create a mock FormGroupDirective +export const createFormGroupDirectiveMock = ( + validator: (Validator | ValidatorFn)[] = [], + asyncValidators: AsyncValidator[] = [] +) => { + const formGroupDirectiveMock = new FormGroupDirective(validator, asyncValidators); + + formGroupDirectiveMock.form = FORM_GROUP_MOCK; + + return formGroupDirectiveMock; +}; + +export const LAYOUT_MOCK: DotCMSContentTypeLayoutRow[] = [ + { + divider: { + clazz: 'com.dotcms.contenttype.model.field.ImmutableRowField', + contentTypeId: 'd46d6404125ac27e6ab68fad09266241', + dataType: 'SYSTEM', + fieldType: 'Row', + fieldTypeLabel: 'Row', + fieldVariables: [], + fixed: false, + iDate: 1697051073000, + id: 'a31ea895f80eb0a3754e4a2292e09a52', + indexed: false, + listed: false, + modDate: 1697051077000, + name: 'fields-0', + readOnly: false, + required: false, + searchable: false, + sortOrder: 0, + unique: false, + variable: 'fields0' + }, + columns: [ + { + columnDivider: { + clazz: 'com.dotcms.contenttype.model.field.ImmutableColumnField', + contentTypeId: 'd46d6404125ac27e6ab68fad09266241', + dataType: 'SYSTEM', + fieldType: 'Column', + fieldTypeLabel: 'Column', + fieldVariables: [], + fixed: false, + iDate: 1697051073000, + id: 'd4c32b4b9fb5b11c58c245d4a02bef47', + indexed: false, + listed: false, + modDate: 1697051077000, + name: 'fields-1', + readOnly: false, + required: false, + searchable: false, + sortOrder: 1, + unique: false, + variable: 'fields1' + }, + fields: [ + { + clazz: 'com.dotcms.contenttype.model.field.ImmutableTextField', + contentTypeId: 'd46d6404125ac27e6ab68fad09266241', + dataType: 'TEXT', + defaultValue: 'Placeholder', + fieldType: 'Text', + fieldTypeLabel: 'Text', + fieldVariables: [], + fixed: false, + hint: 'A hint Text', + iDate: 1697051093000, + id: '1d1505a4569681b923769acb785fd093', + indexed: false, + listed: false, + modDate: 1697051093000, + name: 'name1', + readOnly: false, + required: true, + searchable: false, + sortOrder: 2, + unique: false, + variable: 'name1' + }, + { + clazz: 'com.dotcms.contenttype.model.field.ImmutableTextField', + contentTypeId: 'd46d6404125ac27e6ab68fad09266241', + dataType: 'TEXT', + fieldType: 'Text', + fieldTypeLabel: 'Text', + fieldVariables: [], + fixed: false, + iDate: 1697051107000, + id: 'fc776c45044f2d043f5e98eaae36c9ff', + indexed: false, + listed: false, + modDate: 1697051107000, + name: 'text2', + readOnly: false, + required: true, + searchable: false, + sortOrder: 3, + unique: false, + variable: 'text2' + } + ] + }, + { + columnDivider: { + clazz: 'com.dotcms.contenttype.model.field.ImmutableColumnField', + contentTypeId: 'd46d6404125ac27e6ab68fad09266241', + dataType: 'SYSTEM', + fieldType: 'Column', + fieldTypeLabel: 'Column', + fieldVariables: [], + fixed: false, + iDate: 1697051077000, + id: '848fc78a11e7290efad66eb39333ae2b', + indexed: false, + listed: false, + modDate: 1697051107000, + name: 'fields-2', + readOnly: false, + required: false, + searchable: false, + sortOrder: 4, + unique: false, + variable: 'fields2' + }, + fields: [ + { + clazz: 'com.dotcms.contenttype.model.field.ImmutableTextField', + contentTypeId: 'd46d6404125ac27e6ab68fad09266241', + dataType: 'TEXT', + fieldType: 'Text', + fieldTypeLabel: 'Text', + fieldVariables: [], + fixed: false, + hint: 'A hint text2', + iDate: 1697051118000, + id: '1f6765de8d4ad069ff308bfca56b9255', + indexed: false, + listed: false, + modDate: 1697051118000, + name: 'text3', + readOnly: false, + required: false, + searchable: false, + sortOrder: 5, + unique: false, + variable: 'text3' + } + ] + } + ] + } +]; + +export const CONTENT_TYPE_MOCK: DotCMSContentType = { + baseType: 'CONTENT', + clazz: 'com.dotcms.contenttype.model.type.ImmutableSimpleContentType', + defaultType: false, + fields: [ + { + clazz: 'com.dotcms.contenttype.model.field.ImmutableRowField', + contentTypeId: 'd46d6404125ac27e6ab68fad09266241', + dataType: 'SYSTEM', + fieldType: 'Row', + fieldTypeLabel: 'Row', + fieldVariables: [], + fixed: false, + iDate: 1697051073000, + id: 'a31ea895f80eb0a3754e4a2292e09a52', + indexed: false, + listed: false, + modDate: 1697051077000, + name: 'fields-0', + readOnly: false, + required: false, + searchable: false, + sortOrder: 0, + unique: false, + variable: 'fields0' + }, + { + clazz: 'com.dotcms.contenttype.model.field.ImmutableColumnField', + contentTypeId: 'd46d6404125ac27e6ab68fad09266241', + dataType: 'SYSTEM', + fieldType: 'Column', + fieldTypeLabel: 'Column', + fieldVariables: [], + fixed: false, + iDate: 1697051073000, + id: 'd4c32b4b9fb5b11c58c245d4a02bef47', + indexed: false, + listed: false, + modDate: 1697051077000, + name: 'fields-1', + readOnly: false, + required: false, + searchable: false, + sortOrder: 1, + unique: false, + variable: 'fields1' + }, + { + clazz: 'com.dotcms.contenttype.model.field.ImmutableTextField', + contentTypeId: 'd46d6404125ac27e6ab68fad09266241', + dataType: 'TEXT', + defaultValue: 'Placeholder', + fieldType: 'Text', + fieldTypeLabel: 'Text', + fieldVariables: [], + fixed: false, + hint: 'A hint Text 2', + iDate: 1697051093002, + id: '1d1505a4569681b923769acb785fd094', + indexed: false, + listed: false, + modDate: 1697051093000, + name: 'name13', + readOnly: false, + required: true, + searchable: false, + sortOrder: 2, + unique: false, + variable: 'name13' + }, + { + clazz: 'com.dotcms.contenttype.model.field.ImmutableTextField', + contentTypeId: 'd46d6404125ac27e6ab68fad09266241', + dataType: 'TEXT', + fieldType: 'Text', + fieldTypeLabel: 'Text', + fieldVariables: [], + fixed: false, + iDate: 1697051107001, + id: 'fc776c45044f2d043f5e98eaae36c9f2', + indexed: false, + listed: false, + modDate: 1697051107000, + name: 'text23', + readOnly: false, + required: true, + searchable: false, + sortOrder: 3, + unique: false, + variable: 'text23' + }, + { + clazz: 'com.dotcms.contenttype.model.field.ImmutableColumnField', + contentTypeId: 'd46d6404125ac27e6ab68fad09266241', + dataType: 'SYSTEM', + fieldType: 'Column', + fieldTypeLabel: 'Column', + fieldVariables: [], + fixed: false, + iDate: 1697051077000, + id: '848fc78a11e7290efad66eb39333ae2b', + indexed: false, + listed: false, + modDate: 1697051107000, + name: 'fields-2', + readOnly: false, + required: false, + searchable: false, + sortOrder: 4, + unique: false, + variable: 'fields2' + }, + { + clazz: 'com.dotcms.contenttype.model.field.ImmutableTextField', + contentTypeId: 'd46d6404125ac27e6ab68fad09266241', + dataType: 'TEXT', + fieldType: 'Text', + fieldTypeLabel: 'Text', + fieldVariables: [], + fixed: false, + hint: 'A hint text2', + iDate: 1697051118000, + id: '1f6765de8d4ad069ff308bfca56b9255', + indexed: false, + listed: false, + modDate: 1697051118000, + name: 'text3', + readOnly: false, + required: false, + searchable: false, + sortOrder: 5, + unique: false, + variable: 'text3' + } + ], + fixed: false, + folder: 'SYSTEM_FOLDER', + host: '48190c8c-42c4-46af-8d1a-0cd5db894797', + iDate: 1697051073000, + icon: 'event_note', + id: 'd46d6404125ac27e6ab68fad09266241', + layout: LAYOUT_MOCK, + modDate: 1697051118000, + multilingualable: false, + name: 'Test', + contentType: 'Test', + system: false, + systemActionMappings: {}, + variable: 'Test', + versionable: true, + workflows: [ + { + archived: false, + creationDate: new Date(1697047303976), + defaultScheme: false, + description: '', + entryActionId: null, + id: 'd61a59e1-a49c-46f2-a929-db2b4bfa88b2', + mandatory: false, + modDate: new Date(1697047292887), + name: 'System Workflow', + system: true + } + ], + nEntries: 0 +};