Skip to content

Commit

Permalink
Empty and error message for category field in search mode (#29337)
Browse files Browse the repository at this point in the history
### Proposed Changes
* Add empty and error message for edit content in `search` mode

### Checklist
- [x] Tests
- [x] Translations
- [ ] Security Implications Contemplated (add notes if applicable)

### Ok
<img width="1492" alt="Screenshot 2024-07-23 at 12 50 58 PM"
src="https://github.com/user-attachments/assets/937d16ab-7654-4f05-9560-7b06af458afe">

### Error
<img width="1486" alt="Screenshot 2024-07-23 at 12 50 19 PM"
src="https://github.com/user-attachments/assets/4ef22762-7b11-494a-ac05-54e30f03f1bb">

### Empty
<img width="1487" alt="Screenshot 2024-07-23 at 12 50 51 PM"
src="https://github.com/user-attachments/assets/2cddecb6-dc2f-4237-9f11-3f01d7dab6c1">

---------

Co-authored-by: Nicolas Molina <[email protected]>
  • Loading branch information
oidacra and nicobytes authored Jul 27, 2024
1 parent 0f53ef2 commit b9d33eb
Show file tree
Hide file tree
Showing 16 changed files with 285 additions and 96 deletions.
Binary file removed .nx/cache/18.3.5-nx.darwin-arm64.node
Binary file not shown.
4 changes: 3 additions & 1 deletion core-web/libs/dotcms-models/src/lib/shared-models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
* |-> IDLE = Finished Loading or Saving
* SAVING = Status of an action of the component loaded (delete, saving, editing)
* |-> IDLE = Finished delete, saving, editing
* ERROR = Error state for the component
**/
export enum ComponentStatus {
INIT = 'INIT',
LOADING = 'LOADING',
LOADED = 'LOADED',
SAVING = 'SAVING',
IDLE = 'IDLE'
IDLE = 'IDLE',
ERROR = 'ERROR'
}

export const enum FeaturedFlags {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
<div #tableContainer class="category-field__search-list">
@if (!isLoading()) {
@if (!$isLoading()) {
<p-table
data-testId="categories-table"
[scrollHeight]="$scrollHeight()"
[scrollable]="true"
[value]="categories()"
[value]="$categories()"
dataKey="key"
selectionMode="multiple"
[(selection)]="itemsSelected"
(onHeaderCheckboxToggle)="onHeaderCheckboxToggle($event)"
(onRowSelect)="onSelectItem($event)"
(onRowUnselect)="onRemoveItem($event)"
styleClass="dotTable ">
styleClass="dotTable">
<ng-template pTemplate="header">
<tr data-testId="table-header">
<th id="checkbox" scope="col" data-testId="table-header-checkbox">
Expand Down Expand Up @@ -42,6 +42,16 @@
</td>
</tr>
</ng-template>

<ng-template pTemplate="emptymessage">
<tr>
<td colspan="3">
<dot-empty-container
[configuration]="$emptyOrErrorMessage()"
[hideContactUsLink]="true" />
</td>
</tr>
</ng-template>
</p-table>
} @else {
<dot-table-skeleton
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,31 @@
@use "variables" as *;

.category-field__search-list {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
}

:host ::ng-deep .dotTable.p-datatable > .p-datatable-wrapper {
border-radius: 0;
border: none;
:host {
::ng-deep {
.dotTable.p-datatable > .p-datatable-wrapper {
border-radius: 0;
border: none;
}
}

&.category-field__search-list--empty ::ng-deep {
p-table,
.dotTable.p-datatable,
.p-datatable-wrapper,
table {
height: 100%;

tr:not(.p-highlight):hover {
background: $white;
cursor: auto;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,66 +1,130 @@
import { byTestId, createComponentFactory, mockProvider, Spectator } from '@ngneat/spectator/jest';
import { byTestId, createComponentFactory, Spectator } from '@ngneat/spectator/jest';

import { Table, TableModule } from 'primeng/table';

import { DotMessageService } from '@dotcms/data-access';
import { ComponentStatus } from '@dotcms/dotcms-models';
import { DotEmptyContainerComponent } from '@dotcms/ui';

import { DotCategoryFieldSearchListComponent } from './dot-category-field-search-list.component';

import { CATEGORY_FIELD_EMPTY_MESSAGES } from '../../../../models/dot-edit-content-field.constant';
import { MockResizeObserver } from '../../../../utils/mocks';
import { CATEGORY_MOCK_TRANSFORMED } from '../../mocks/category-field.mocks';

const mockMessageService = {
get: jest.fn((key: string) => `${key}`)
};

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

const createComponent = createComponentFactory({
component: DotCategoryFieldSearchListComponent,
providers: [mockProvider(DotMessageService)]
providers: [{ provide: DotMessageService, useValue: mockMessageService }],
imports: [TableModule]
});

beforeEach(() => {
spectator = createComponent({
detectChanges: false,
props: {
categories: CATEGORY_MOCK_TRANSFORMED,
selected: CATEGORY_MOCK_TRANSFORMED,
isLoading: false
}
categories: CATEGORY_MOCK_TRANSFORMED,
status: ComponentStatus.LOADED
} as unknown
});

spectator.detectChanges();
});

beforeAll(() => {
global.ResizeObserver = MockResizeObserver;
});

afterEach(() => {
jest.resetAllMocks();
});

it('should show the skeleton if the component is loading', () => {
spectator.setInput('isLoading', true);
spectator.setInput('status', ComponentStatus.LOADING);
spectator.detectChanges();
expect(spectator.query(byTestId('categories-skeleton'))).not.toBeNull();
expect(spectator.query(byTestId('categories-table'))).toBeNull();
});

it('should show the table if the component is not loading', () => {
spectator.setInput('isLoading', false);
spectator.setInput('status', ComponentStatus.LOADED);
spectator.detectChanges();
expect(spectator.query(byTestId('categories-table'))).not.toBeNull();
expect(spectator.query(byTestId('categories-skeleton'))).toBeNull();
});

it('should render table header', () => {
spectator.detectChanges();
const rows = spectator.queryAll(byTestId('table-header'));
expect(rows.length).toBe(1);
});

it('should render table header with 3 columns, checkbox, name of category and parent path', () => {
spectator.detectChanges();
expect(spectator.query(byTestId('table-header-checkbox'))).not.toBeNull();
expect(spectator.query(byTestId('table-header-category-name'))).not.toBeNull();
expect(spectator.query(byTestId('table-header-parents'))).not.toBeNull();
});

it('should render table with categories', () => {
spectator.detectChanges();
const rows = spectator.queryAll(byTestId('table-row'));
expect(rows.length).toBe(CATEGORY_MOCK_TRANSFORMED.length);
});

it('should render `dot-empty-container` with `empty` configuration ', () => {
const expectedConfig = CATEGORY_FIELD_EMPTY_MESSAGES.empty;
spectator.setInput('status', ComponentStatus.LOADED);
spectator.setInput('categories', []);
spectator.detectChanges();

expect(spectator.query(DotEmptyContainerComponent)).not.toBeNull();
expect(spectator.component.$emptyOrErrorMessage()).toEqual(expectedConfig);
});

it('should render `dot-empty-container` with `error` configuration ', () => {
const expectedConfig = CATEGORY_FIELD_EMPTY_MESSAGES[ComponentStatus.ERROR];
spectator.setInput('status', ComponentStatus.ERROR);
spectator.setInput('categories', []);

spectator.detectChanges();
expect(spectator.query(DotEmptyContainerComponent)).not.toBeNull();
expect(spectator.component.$emptyOrErrorMessage()).toEqual(expectedConfig);
});

it('should emit $itemChecked event when an item is selected', async () => {
const itemCheckedSpy = jest.spyOn(spectator.component.itemChecked, 'emit');
spectator.detectChanges();
spectator.triggerEventHandler(Table, 'onRowSelect', { data: CATEGORY_MOCK_TRANSFORMED[0] });
expect(itemCheckedSpy).toHaveBeenCalledWith(CATEGORY_MOCK_TRANSFORMED[0]);
});

it('should emit $removeItem event when an item is unselected', () => {
const removeItemSpy = jest.spyOn(spectator.component.removeItem, 'emit');
spectator.detectChanges();
spectator.triggerEventHandler(Table, 'onRowUnselect', {
data: CATEGORY_MOCK_TRANSFORMED[0]
});
expect(removeItemSpy).toHaveBeenCalledWith(CATEGORY_MOCK_TRANSFORMED[0].key);
});

it('should emit $itemChecked event with all items when header checkbox is selected', () => {
const itemCheckedSpy = jest.spyOn(spectator.component.itemChecked, 'emit');

spectator.detectChanges();
spectator.triggerEventHandler(Table, 'onHeaderCheckboxToggle', { checked: true });

expect(itemCheckedSpy).toHaveBeenCalledWith(CATEGORY_MOCK_TRANSFORMED);
});

it('should emit $removeItem event with all keys when header checkbox is unselected', () => {
const removeItemSpy = jest.spyOn(spectator.component.removeItem, 'emit');
spectator.detectChanges();
spectator.triggerEventHandler(Table, 'onHeaderCheckboxToggle', { checked: true });
spectator.triggerEventHandler(Table, 'onHeaderCheckboxToggle', { checked: false });

const allKeys = CATEGORY_MOCK_TRANSFORMED.map((category) => category.key);
expect(removeItemSpy).toHaveBeenCalledWith(allKeys);
});
});
Loading

0 comments on commit b9d33eb

Please sign in to comment.