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

feat(editor-content): Limit the amount categories #29208

Merged
merged 10 commits into from
Jul 12, 2024
1 change: 0 additions & 1 deletion core-web/libs/edit-content/jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ export default {
preset: '../../jest.preset.js',
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
globals: {},
coverageDirectory: '../../coverage/libs/edit-content',
transform: {
'^.+\\.(ts|mjs|js|html)$': [
'jest-preset-angular',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<div class="flex gap-3 align-items-center flex-wrap" data-testId="category-list">
@for (category of $categoriesToShow(); track category.key) {
<p-chip
[pTooltip]="category.value"
[removable]="true"
[label]="category.value"
tooltipPosition="top"
styleClass="p-chip-sm" />
} @if ($showAllBtn()) {
<button
(click)="toogleShowAll()"
[label]="$btnLabel()"
class="p-button-sm p-button-text p-button-secondary"
data-testId="show-btn"
pButton></button>
}
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';

import { ButtonModule, ButtonDirective } from 'primeng/button';
import { ChipModule, Chip } from 'primeng/chip';

import { DotMessageService } from '@dotcms/data-access';

import { DotCategoryFieldChipsComponent } from './dot-category-field-chips.component';

import { MAX_CHIPS } from '../../dot-edit-content-category-field.const';
import { CATEGORIES_KEY_VALUE, CATEGORY_MESSAGE_MOCK } from '../../mocks/category-field.mocks';

describe('DotCategoryFieldChipsComponent', () => {
let spectator: Spectator<DotCategoryFieldChipsComponent>;

const createComponent = createComponentFactory({
component: DotCategoryFieldChipsComponent,
providers: [
{
provide: DotMessageService,
useValue: CATEGORY_MESSAGE_MOCK
}
],
imports: [ChipModule, ButtonModule]
});

beforeEach(() => {
spectator = createComponent({
detectChanges: false,
props: {
categories: CATEGORIES_KEY_VALUE
} as unknown as DotCategoryFieldChipsComponent
});
});

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

it('should the max input be equal to constant by default', () => {
spectator.detectChanges();
expect(spectator.component.$max()).toBe(MAX_CHIPS);
});

it('should be show a max of categories', () => {
spectator.setInput('max', 2);
spectator.detectChanges();
const chips = spectator.queryAll(Chip);
expect(chips.length).toBe(2);
});

it('should be show all categories', () => {
spectator.setInput('max', 2);
spectator.component.$showAll.set(true);
spectator.detectChanges();
const chips = spectator.queryAll(Chip);
expect(chips.length).toBe(CATEGORIES_KEY_VALUE.length);
});

it('should be show the more btn with the proper label', () => {
spectator.setInput('max', 2);
spectator.detectChanges();
const showBtn = spectator.query(ButtonDirective);
const size = spectator.component.$categories().length - spectator.component.$max();
expect(showBtn.label).toBe(`${size} More`);
});

it('should be show the less btn with the proper label', () => {
spectator.setInput('max', 2);
spectator.component.$showAll.set(true);
spectator.detectChanges();
const showBtn = spectator.query(ButtonDirective);
expect(showBtn.label).toBe(`Less`);
});

it('should not show a btn and the label be null', () => {
spectator.setInput('max', CATEGORIES_KEY_VALUE.length + 1);
spectator.detectChanges();
const showBtn = spectator.query(ButtonDirective);
const label = spectator.component.$btnLabel();
expect(showBtn).toBeNull();
expect(label).toBeNull();
});

describe('toogleShowAll', () => {
it('should set showAll to true', () => {
spectator.component.$showAll.set(false);
spectator.component.toogleShowAll();
expect(spectator.component.$showAll()).toBe(true);
});

it('should set showAll to false', () => {
spectator.component.$showAll.set(true);
spectator.component.toogleShowAll();
expect(spectator.component.$showAll()).toBe(false);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { ChangeDetectionStrategy, Component, computed, inject, input, signal } from '@angular/core';

import { ButtonModule } from 'primeng/button';
import { ChipModule } from 'primeng/chip';
import { TooltipModule } from 'primeng/tooltip';

import { DotMessageService } from '@dotcms/data-access';

import { MAX_CHIPS } from '../../dot-edit-content-category-field.const';
import { DotCategoryFieldKeyValueObj } from '../../models/dot-category-field.models';

/**
* Represents the Dot Category Field Chips component.
*
* @export
* @class DotCategoryFieldChipsComponent
*/
@Component({
selector: 'dot-category-field-chips',
standalone: true,
imports: [ButtonModule, ChipModule, TooltipModule],
templateUrl: './dot-category-field-chips.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DotCategoryFieldChipsComponent {
/**
* Represents the variable 'dotMessageService' which is of type 'DotMessageService'.
*
* @memberof DotCategoryFieldChipsComponent
*/
readonly #dotMessageService = inject(DotMessageService);
/**
* Represents the variable 'showAll' which is of type 'signal<boolean>'.
*
* @memberof DotCategoryFieldChipsComponent
*/
$showAll = signal(false);
/**
* Represents the variable 'max' which is of type 'number'.
*
* @memberof DotCategoryFieldChipsComponent
*/
$max = input<number>(MAX_CHIPS, { alias: 'max' });
/**
* Represents the variable 'categories' which is of type 'DotCategoryFieldKeyValueObj[]'.
*
* @memberof DotCategoryFieldChipsComponent
*/
$categories = input.required<DotCategoryFieldKeyValueObj[]>({ alias: 'categories' });
/**
* Represents the variable 'label' which is of type 'string'.
*
* @memberof DotCategoryFieldChipsComponent
*/
$categoriesToShow = computed(() => {
nicobytes marked this conversation as resolved.
Show resolved Hide resolved
const categories = this.$categories();
if (this.$showAll()) {
return categories;
}

return categories.slice(0, this.$max());
});
/**
* Represents the variable '$showAllBtn' which is of type 'computed<boolean>'.
*
* @memberof DotCategoryFieldChipsComponent
*/
$showAllBtn = computed(() => {
const size = this.$categories().length;

if (size > this.$max()) {
return true;
}

return false;
});
/**
* Represents the variable 'btnLabel' which is of type 'computed<string>'.
*
* @memberof DotCategoryFieldChipsComponent
*/
$btnLabel = computed(() => {
const size = this.$categories().length;
const max = this.$max();

if (this.$showAll()) {
return this.#dotMessageService.get('edit.content.category-field.list.show.less');
}

if (size > max) {
return this.#dotMessageService.get(
'edit.content.category-field.list.show.more',
`${size - max}`
);
}

return null;
});
/**
* Method to toogle the show all categories.
*
* @memberof DotCategoryFieldChipsComponent
*/
toogleShowAll(): void {
this.$showAll.update((showAll) => !showAll);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import { DotMessagePipe } from '@dotcms/ui';

import { CategoryFieldStore } from '../../store/content-category-field.store';
import { DotCategoryFieldCategoryListComponent } from '../dot-category-field-category-list/dot-category-field-category-list.component';

/**
* The DotCategoryFieldSidebarComponent is a sidebar panel that allows editing of content category field.
* It provides interfaces for item selection and click handling, and communicates with a store
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
@if (store.selected().length) {
<div class="dot-category-field__categories" data-testId="category-chip-list">
@for (category of store.selected(); track category.key) {
<p-chip
[pTooltip]="category.value"
[removable]="true"
[label]="category.value"
tooltipPosition="top"
styleClass="p-chip-sm" />
}
</div>
<div class="dot-category-field__categories" data-testId="category-chip-list">
<dot-category-field-chips [categories]="store.selected()" />
</div>
}

<div class="dot-category-field__select">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,13 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { ControlContainer, FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';

import { ButtonModule } from 'primeng/button';
import { ChipModule } from 'primeng/chip';
import { ChipsModule } from 'primeng/chips';
import { TooltipModule } from 'primeng/tooltip';

import { delay } from 'rxjs/operators';

import { DotCMSContentlet, DotCMSContentTypeField } from '@dotcms/dotcms-models';
import { DotDynamicDirective, DotMessagePipe } from '@dotcms/ui';

import { DotCategoryFieldChipsComponent } from './components/dot-category-field-chips/dot-category-field-chips.component';
import { DotCategoryFieldSidebarComponent } from './components/dot-category-field-sidebar/dot-category-field-sidebar.component';
import { CLOSE_SIDEBAR_CSS_DELAY_MS } from './dot-edit-content-category-field.const';
import { CategoriesService } from './services/categories.service';
Expand All @@ -40,14 +38,12 @@ import { CategoryFieldStore } from './store/content-category-field.store';
selector: 'dot-edit-content-category-field',
standalone: true,
imports: [
ChipsModule,
ReactiveFormsModule,
ButtonModule,
ChipModule,
NgClass,
TooltipModule,
DotMessagePipe,
DotDynamicDirective
DotDynamicDirective,
DotCategoryFieldChipsComponent
],
templateUrl: './dot-edit-content-category-field.component.html',
styleUrl: './dot-edit-content-category-field.component.scss',
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export const CLOSE_SIDEBAR_CSS_DELAY_MS = 300;

export const MAX_CHIPS = 10;
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { DotCMSContentlet, DotCMSContentTypeField } from '@dotcms/dotcms-models';
import { MockDotMessageService } from '@dotcms/utils-testing';

import { DotCategoryFieldCategory } from '../models/dot-category-field.models';
import {
DotCategoryFieldCategory,
DotCategoryFieldKeyValueObj
} from '../models/dot-category-field.models';

export const CATEGORY_FIELD_VARIABLE_NAME = 'categorias';

Expand Down Expand Up @@ -203,3 +207,43 @@ export const CATEGORY_LIST_MOCK: DotCategoryFieldCategory[][] = [
* Represent the selected categories
*/
export const SELECTED_LIST_MOCK = [CATEGORY_LEVEL_1[1].inode, CATEGORY_LEVEL_1[2].inode];

export const CATEGORIES_KEY_VALUE: DotCategoryFieldKeyValueObj[] = [
{
key: '0ab5e687775e4793679970e561380560',
value: 'Electrical',
path: 'Electrical'
},
{
key: 'cb83dc32c0a198fd0ca427b3b587f4ce',
value: 'Doors & Windows',
path: 'Doors & Windows'
},
{
key: '1f208488057007cedda0e0b5d52ee3b3',
value: 'Cleaning Supplies',
path: 'Cleaning Supplies'
},
{
key: 'd2fb8e67c390e3b84cd613fa15aad5d4',
value: 'Concrete & Cement',
path: 'Concrete & Cement'
},
{
key: '3a3effac9f26593810c8687e692817a6',
value: 'Flooring',
path: 'Flooring'
},
{
key: '977ba2c4e2af65e303c748ec39f0f1ca',
value: 'Garage Organization',
path: 'Garage Organization'
}
];

const MESSAGES_MOCK = {
'edit.content.category-field.list.show.less': 'Less',
'edit.content.category-field.list.show.more': '{0} More'
};

export const CATEGORY_MESSAGE_MOCK = new MockDotMessageService(MESSAGES_MOCK);
2 changes: 2 additions & 0 deletions dotCMS/src/main/webapp/WEB-INF/messages/Language.properties
Original file line number Diff line number Diff line change
Expand Up @@ -5746,3 +5746,5 @@ content.type.form.banner.message= Enable <b>Edit Content Beta</b> for a fresh ed
edit.content.category-field.show-categories-dialog=Select
edit.content.category-field.sidebar.header.select-categories=Select categories
edit.content.category-field.sidebar.button.clear-all=Clear all
edit.content.category-field.list.show.more={0} More
edit.content.category-field.list.show.less=Less
Loading