Skip to content

Commit

Permalink
feat(edit-content) add tests #28879
Browse files Browse the repository at this point in the history
  • Loading branch information
oidacra committed Jul 12, 2024
1 parent 56e90a2 commit 8a25565
Show file tree
Hide file tree
Showing 10 changed files with 136 additions and 38 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
<div #tableContainer class="category-field__search-list">
{{ categories() | json }}
@if (!isLoading()) {
<p-table
data-testId="categories-table"
[scrollHeight]="$scrollHeight()"
[scrollable]="true"
[value]="$searchResults()"
[value]="categories()"
dataKey="key"
selectionMode="multiple"
[(selection)]="itemsSelected"
Expand All @@ -12,17 +14,21 @@
(onRowUnselect)="onRemoveItem($event)"
styleClass="dotTable ">
<ng-template pTemplate="header">
<tr>
<th>
<tr data-testId="table-header">
<th id="checkbox" scope="col" data-testId="table-header-checkbox">
<p-tableHeaderCheckbox style="width: 4rem"></p-tableHeaderCheckbox>
</th>
<th>{{ 'edit.content.category-field.search.name' | dm }}</th>
<th>{{ 'edit.content.category-field.search.assignee' | dm }}</th>
<th data-testId="table-header-category-name" id="category-name" scope="col">
{{ 'edit.content.category-field.search.name' | dm }}
</th>
<th id="parents" data-testId="table-header-parents" scope="col">
{{ 'edit.content.category-field.search.assignee' | dm }}
</th>
</tr>
</ng-template>

<ng-template let-category pTemplate="body">
<tr>
<tr data-testId="table-row">
<td>
<p-tableCheckbox [value]="category"></p-tableCheckbox>
</td>
Expand All @@ -33,6 +39,7 @@
</p-table>
} @else {
<dot-table-skeleton
data-testId="categories-skeleton"
[columns]="[
'',
'edit.content.category-field.search.name' | dm,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
overflow: hidden;
}

::ng-deep .dotTable.p-datatable > .p-datatable-wrapper {
:host ::ng-deep .dotTable.p-datatable > .p-datatable-wrapper {
border-radius: 0;
border: none;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createComponentFactory, mockProvider, Spectator } from '@ngneat/spectator/jest';
import { byTestId, createComponentFactory, mockProvider, Spectator } from '@ngneat/spectator/jest';

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

Expand Down Expand Up @@ -34,7 +34,33 @@ describe('DotCategoryFieldSearchListComponent', () => {
jest.resetAllMocks();
});

it('should create', () => {
expect(spectator.component).toBeTruthy();
it('should show the skeleton if the component is loading', () => {
spectator.setInput('isLoading', true);
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.detectChanges();
expect(spectator.query(byTestId('categories-table'))).not.toBeNull();
expect(spectator.query(byTestId('categories-skeleton'))).toBeNull();
});

it('should render table header', () => {
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', () => {
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', () => {
const rows = spectator.queryAll(byTestId('table-row'));
expect(rows.length).toBe(CATEGORY_MOCK_TRANSFORMED.length);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
AfterViewInit,
ChangeDetectionStrategy,
Component,
computed,
effect,
ElementRef,
EventEmitter,
Expand All @@ -22,15 +21,13 @@ import { TableModule } from 'primeng/table';

import { debounceTime } from 'rxjs/operators';

import { DotCategory } from '@dotcms/dotcms-models';
import { DotMessagePipe } from '@dotcms/ui';

import {
DotCategoryFieldKeyValueObj,
DotTableHeaderCheckboxSelectEvent,
DotTableRowSelectEvent
} from '../../models/dot-category-field.models';
import { getParentPath } from '../../utils/category-field.utils';
import { DotTableSkeletonComponent } from '../dot-table-skeleton/dot-table-skeleton.component';

@Component({
Expand All @@ -55,7 +52,8 @@ export class DotCategoryFieldSearchListComponent implements AfterViewInit, OnDes
/**
* Represents the categories found with the filter
*/
categories = input.required<DotCategory[]>();
categories = input.required<DotCategoryFieldKeyValueObj[]>();

/**
* Represent the selected items in the store
*/
Expand All @@ -74,17 +72,7 @@ export class DotCategoryFieldSearchListComponent implements AfterViewInit, OnDes
* Represents a variable indicating if the component is in loading state.
*/
isLoading = input.required<boolean>();
/**
* Computed variable to store the search results parsed.
*
*/
$searchResults = computed<DotCategoryFieldKeyValueObj[]>(() => {
return this.categories().map((item) => {
const path = getParentPath(item);

return { key: item.key, value: item.categoryName, path: path, inode: item.inode };
});
});
/**
* Model of the items selected
*/
Expand Down Expand Up @@ -137,8 +125,8 @@ export class DotCategoryFieldSearchListComponent implements AfterViewInit, OnDes
*/
onHeaderCheckboxToggle({ checked }: DotTableHeaderCheckboxSelectEvent): void {
if (checked) {
const values = this.$searchResults().map((item) => item.key);
this.itemChecked.emit(this.$searchResults());
const values = this.categories().map((item) => item.key);
this.itemChecked.emit(this.categories());
this.temporarySelectedAll = [...values];
} else {
this.removeItem.emit(this.temporarySelectedAll);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,27 @@
[formControl]="searchControl"
[placeholder]="'edit.content.category-field.search.input.placeholder' | dm"
class="category-field__search-input"
data-testId="search-input"
pInputText
type="text" />
@if (searchControl.value && !isLoading()) {
<span (click)="clearInput()" class="p-inputgroup-addon category-field__search-icon-clear">
<span
data-testId="search-icon-clear"
(click)="clearInput()"
class="p-inputgroup-addon category-field__search-icon-clear">
<i class="pi pi-times cursor-pointer"></i>
</span>
} @if (isLoading()) {
<span class="p-inputgroup-addon category-field__search-icon-loading">
<span
data-testId="search-icon-loading"
class="p-inputgroup-addon category-field__search-icon-loading">
<i class="pi pi-spin pi-spinner"></i>
</span>
}

<span class="p-inputgroup-addon category-field__search--icon-search">
<span
class="p-inputgroup-addon category-field__search--icon-search"
data-testId="search-icon-search">
<i class="pi pi-search"></i>
</span>
</div>
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { expect } from '@jest/globals';
import { createComponentFactory, mockProvider, Spectator } from '@ngneat/spectator/jest';
import { byTestId, createComponentFactory, mockProvider, Spectator } from '@ngneat/spectator/jest';

import { discardPeriodicTasks, fakeAsync } from '@angular/core/testing';

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

import { DotCategoryFieldSearchComponent } from './dot-category-field-search.component';
import {
DEBOUNCE_TIME,
DotCategoryFieldSearchComponent
} from './dot-category-field-search.component';

const TERM_TO_SEARCH = 'Wood';
describe('DotCategoryFieldSearchComponent', () => {
let spectator: Spectator<DotCategoryFieldSearchComponent>;

Expand All @@ -27,7 +32,61 @@ describe('DotCategoryFieldSearchComponent', () => {
jest.resetAllMocks();
});

it('should .....', () => {
expect(spectator.component).not.toBeNull();
it('should show only the search icon', () => {
spectator.detectChanges();

expect(spectator.query(byTestId('search-icon-search'))).toBeTruthy();
expect(spectator.query(byTestId('search-icon-clear'))).not.toBeTruthy();
expect(spectator.query(byTestId('search-icon-loading'))).not.toBeTruthy();
});

it('should emit "term" with correct value on input change', fakeAsync(() => {
const termSpy = jest.spyOn(spectator.component.term, 'emit');
const input = spectator.query(byTestId('search-input')) as HTMLInputElement;

spectator.typeInElement(TERM_TO_SEARCH, input);

spectator.tick(DEBOUNCE_TIME + 100);

expect(termSpy).toHaveBeenCalledWith(TERM_TO_SEARCH);

discardPeriodicTasks();
}));

it('should clear input and emit "changeMode" when clear icon is clicked', fakeAsync(() => {
const changeModeSpy = jest.spyOn(spectator.component.changeMode, 'emit');
const input = spectator.query(byTestId('search-input')) as HTMLInputElement;
spectator.typeInElement(TERM_TO_SEARCH, input);
spectator.tick(DEBOUNCE_TIME + 100);

spectator.detectChanges();

spectator.click(spectator.query(byTestId('search-icon-clear')));

expect(input.value).toBe('');
expect(changeModeSpy).toHaveBeenCalledWith('list');
discardPeriodicTasks();
}));

it('should show loading icon when isLoading is true', () => {
spectator.setInput('isLoading', true);
spectator.detectChanges();

expect(spectator.query(byTestId('search-icon-clear'))).not.toBeTruthy();
expect(spectator.query(byTestId('search-icon-loading'))).toBeTruthy();
});

it('should show clear icon when there is input and not loading', fakeAsync(() => {
const input = spectator.query(byTestId('search-input')) as HTMLInputElement;
spectator.typeInElement('search term', input);
spectator.tick(DEBOUNCE_TIME + 100);

spectator.setInput('isLoading', false);
spectator.detectChanges();

const clearIcon = spectator.query(byTestId('search-icon-clear'));
expect(clearIcon).toBeTruthy();

discardPeriodicTasks();
}));
});
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import { debounceTime, filter } from 'rxjs/operators';

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

const DEBOUNCE_TIME = 300;
export const DEBOUNCE_TIME = 300;

const MINIMUM_CHARACTERS = 3;

@Component({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
(itemChecked)="store.addSelected($event)"
(removeItem)="store.removeSelected($event)"
[isLoading]="store.isSearchLoading()"
[categories]="store.searchCategories()"
[categories]="store.searchCategoryList()"
[selected]="store.selected()" />
}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,13 @@ export const CategoryFieldStore = signalStore(
* Clears all categories from the store, effectively resetting state related to categories and their parent paths.
*/
clean() {
patchState(store, { categories: [], keyParentPath: [], mode: 'list', filter: '' });
patchState(store, {
categories: [],
keyParentPath: [],
mode: 'list',
filter: '',
searchCategories: []
});
},

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,15 @@ export const transformCategories = (
const { key, inode, categoryName, childrenCount } = category;
const hasChildren = childrenCount > 0;

const path = category.parentList ? getParentPath(category) : '';

return {
key,
inode,
value: categoryName,
hasChildren,
clicked: hasChildren && keyParentPath.includes(key)
clicked: hasChildren && keyParentPath.includes(key),
path
};
};

Expand Down

0 comments on commit 8a25565

Please sign in to comment.