Skip to content

Commit

Permalink
NAS-129015 / 24.10 / Edit scenario on main dashboard (#10068)
Browse files Browse the repository at this point in the history
  • Loading branch information
undsoft authored May 17, 2024
1 parent 24cec7e commit f9830f2
Show file tree
Hide file tree
Showing 14 changed files with 172 additions and 92 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
></ix-label>

<div class="icon-group" [attr.aria-label]="label">
@for (option of options | keyvalue; track option) {
@for (option of options | keyvalue: keepOrder; track option) {
<button
mat-icon-button
type="button"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { ReactiveFormsModule } from '@angular/forms';
import { FormControl } from '@ngneat/reactive-forms';
import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest';
import { IxIconGroupComponent } from 'app/modules/ix-forms/components/ix-icon-group/ix-icon-group.component';
import { IxIconGroupHarness } from 'app/modules/ix-forms/components/ix-icon-group/ix-icon-group.harness';
import { IxLabelHarness } from 'app/modules/ix-forms/components/ix-label/ix-label.harness';
import { IxFormsModule } from 'app/modules/ix-forms/ix-forms.module';

describe('IxIconGroupComponent', () => {
let spectator: SpectatorHost<IxIconGroupComponent>;
let loader: HarnessLoader;
let iconGroupHarness: IxIconGroupHarness;
const formControl = new FormControl();
const createHost = createHostFactory({
component: IxIconGroupComponent,
imports: [
ReactiveFormsModule,
IxFormsModule,
],
declareComponent: false,
});

beforeEach(async () => {
spectator = createHost(
`<ix-icon-group
[options]="options"
[label]="label"
[tooltip]="tooltip"
[required]="required"
[formControl]="formControl"
></ix-icon-group>`,
{
hostProps: {
formControl,
options: new Map<string, string>([
['edit', 'mdi-pencil'],
['delete', 'mdi-delete'],
]),
label: 'Icon group',
tooltip: 'This is a tooltip',
required: true,
},
},
);

loader = TestbedHarnessEnvironment.loader(spectator.fixture);
iconGroupHarness = await loader.getHarness(IxIconGroupHarness);
});

describe('rendering', () => {
it('renders ix-label and passes label, hint, tooltip and required', async () => {
const label = await loader.getHarness(IxLabelHarness.with({ label: 'Icon group' }));

expect(label).toBeTruthy();
expect(await label.isRequired()).toBe(true);

const tooltip = await label.getTooltip();
expect(tooltip).toBeTruthy();
expect(await tooltip.getMessage()).toBe('This is a tooltip');
});

it('shows buttons for provided options', async () => {
const buttons = await iconGroupHarness.getButtons();
expect(buttons).toHaveLength(2);

const icons = await iconGroupHarness.getIcons();
expect(icons).toHaveLength(2);
expect(await icons[0].getName()).toBe('mdi-pencil');
expect(await icons[1].getName()).toBe('mdi-delete');
});

it('does not highlight any buttons when no value is set', async () => {
expect(await iconGroupHarness.getValue()).toBe('');
});

it('highlights button with selected value', async () => {
formControl.setValue('edit');
expect(await iconGroupHarness.getValue()).toBe('edit');
});
});

it('updates form control value when user presses the button', async () => {
const buttons = await iconGroupHarness.getButtons();
await buttons[1].click();
expect(formControl.value).toBe('delete');
});

it('disables buttons when form control is disabled', async () => {
formControl.disable();
expect(await iconGroupHarness.isDisabled()).toBe(true);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { UntilDestroy } from '@ngneat/until-destroy';
export class IxIconGroupComponent implements ControlValueAccessor {
@Input({ required: true }) options: Map<string, string>;
@Input() label: string;
@Input() hint: string;
@Input() tooltip: string;
@Input() required: boolean;

Expand Down Expand Up @@ -53,4 +52,8 @@ export class IxIconGroupComponent implements ControlValueAccessor {
this.writeValue(value);
this.onChange(this.value);
}

protected keepOrder(): number {
return 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
BaseHarnessFilters, ComponentHarness, HarnessPredicate, parallel,
} from '@angular/cdk/testing';
import { MatButtonHarness } from '@angular/material/button/testing';
import { MatIconHarness } from '@angular/material/icon/testing';
import { IxLabelHarness } from 'app/modules/ix-forms/components/ix-label/ix-label.harness';
import { IxFormControlHarness } from 'app/modules/ix-forms/interfaces/ix-form-control-harness.interface';
import { getErrorText } from 'app/modules/ix-forms/utils/harness.utils';
Expand All @@ -19,6 +20,7 @@ export class IxIconGroupHarness extends ComponentHarness implements IxFormContro
}

getButtons = this.locatorForAll(MatButtonHarness);
getIcons = this.locatorForAll(MatIconHarness.with({ ancestor: '.icon-group' }));
getErrorText = getErrorText;

async getLabelText(): Promise<string> {
Expand All @@ -29,9 +31,13 @@ export class IxIconGroupHarness extends ComponentHarness implements IxFormContro
return label.getLabel();
}

async getValue(): Promise<string> {
const selectedButton = this.locatorFor(MatButtonHarness.with({ selector: '.selected' }))();
return (await (await selectedButton).host()).getAttribute('aria-label');
async getValue(): Promise<string | undefined> {
const selectedButton = await this.locatorForOptional(MatButtonHarness.with({ selector: '.selected' }))();
if (!selectedButton) {
return '';
}

return (await selectedButton.host()).getAttribute('aria-label');
}

async setValue(value: string): Promise<void> {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
BaseHarnessFilters, ComponentHarness, HarnessPredicate,
} from '@angular/cdk/testing';
import { IxTooltipHarness } from 'app/modules/tooltip/tooltip.harness';

export interface IxLabelFilters extends BaseHarnessFilters {
label: string;
Expand All @@ -9,6 +10,8 @@ export interface IxLabelFilters extends BaseHarnessFilters {
export class IxLabelHarness extends ComponentHarness {
static hostSelector = 'ix-label';

readonly getTooltip = this.locatorFor(IxTooltipHarness);

static with(options: IxLabelFilters): HarnessPredicate<IxLabelHarness> {
return new HarnessPredicate(IxLabelHarness, options)
.addOption('label', options.label, (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label));
Expand All @@ -22,4 +25,9 @@ export class IxLabelHarness extends ComponentHarness {

return label.text({ exclude: '.required' });
}

async isRequired(): Promise<boolean> {
const required = await this.locatorForOptional('.required')();
return Boolean(required);
}
}
10 changes: 10 additions & 0 deletions src/app/modules/tooltip/tooltip.harness.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ComponentHarness } from '@angular/cdk/testing';

export class IxTooltipHarness extends ComponentHarness {
static hostSelector = 'ix-tooltip';

async getMessage(): Promise<string> {
const message = await this.locatorForOptional('.tooltip-message')();
return message ? message.text() : '';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
[totalGroups]="renderedGroups().length"
(moveUp)="onMoveGroup(i, -1)"
(moveDown)="onMoveGroup(i, 1)"
(edit)="onEditGroup(group)"
(edit)="onEditGroup(i)"
(delete)="onDeleteGroup(group)"
></ix-widget-group-controls>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { WidgetGroupComponent } from 'app/pages/dashboard/components/widget-grou
import { WidgetGroupFormComponent } from 'app/pages/dashboard/components/widget-group-form/widget-group-form.component';
import { DashboardStore } from 'app/pages/dashboard/services/dashboard.store';
import { WidgetGroup, WidgetGroupLayout } from 'app/pages/dashboard/types/widget-group.interface';
import { IxChainedSlideInService } from 'app/services/ix-chained-slide-in.service';
import { ChainedComponentResponse, IxChainedSlideInService } from 'app/services/ix-chained-slide-in.service';

describe('DashboardComponent', () => {
const groupA: WidgetGroup = { layout: WidgetGroupLayout.Full, slots: [] };
Expand Down Expand Up @@ -89,6 +89,23 @@ describe('DashboardComponent', () => {
.toHaveBeenCalledWith(WidgetGroupFormComponent, true, groupA);
});

it('updates a widget group after group is edited in WidgetGroupComponent', async () => {
const updatedGroup = { ...groupA, layout: WidgetGroupLayout.Halves };

jest.spyOn(spectator.inject(IxChainedSlideInService), 'open')
.mockReturnValue(of({ response: updatedGroup } as ChainedComponentResponse));

const editIcon = await loader.getHarness(IxIconHarness.with({ name: 'edit' }));
await editIcon.click();

const groups = spectator.queryAll(WidgetGroupComponent);
expect(groups).toHaveLength(4);
expect(groups[0].group).toEqual(updatedGroup);
expect(groups[1].group).toEqual(groupB);
expect(groups[2].group).toEqual(groupC);
expect(groups[3].group).toEqual(groupD);
});

it('removes a widget when delete button is pressed', async () => {
const deleteIcon = await loader.getHarness(IxIconHarness.with({ name: 'delete' }));
await deleteIcon.click();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { TranslateService } from '@ngx-translate/core';
import { EmptyType } from 'app/enums/empty-type.enum';
import { EmptyConfig } from 'app/interfaces/empty-config.interface';
import { SnackbarService } from 'app/modules/snackbar/services/snackbar.service';
import { WidgetGroupFormComponent } from 'app/pages/dashboard/components/widget-group-form/widget-group-form.component';
import { DashboardStore } from 'app/pages/dashboard/services/dashboard.store';
import { WidgetGroup } from 'app/pages/dashboard/types/widget-group.interface';
Expand Down Expand Up @@ -55,14 +56,14 @@ export class DashboardComponent implements OnInit {
private slideIn: IxChainedSlideInService,
private errorHandler: ErrorHandlerService,
private translate: TranslateService,
private snackbar: SnackbarService,
) {}

ngOnInit(): void {
this.dashboardStore.entered();
this.loadGroups();
}

// TODO: Enter configuration mode. Probably store layout that is being edited in a new service.
protected onConfigure(): void {
this.isEditing.set(true);
}
Expand All @@ -77,17 +78,27 @@ export class DashboardComponent implements OnInit {
.open(WidgetGroupFormComponent, true)
.pipe(untilDestroyed(this))
.subscribe((response: ChainedComponentResponse) => {
if (response.response) {
this.renderedGroups.update((groups) => [...groups, response.response as WidgetGroup]);
if (!response.response) {
return;
}

this.renderedGroups.update((groups) => [...groups, response.response as WidgetGroup]);
});
}

protected onEditGroup(group: WidgetGroup): void {
protected onEditGroup(i: number): void {
const editedGroup = this.renderedGroups()[i];
this.slideIn
.open(WidgetGroupFormComponent, true, group)
.open(WidgetGroupFormComponent, true, editedGroup)
.pipe(untilDestroyed(this))
.subscribe(() => {
.subscribe((response) => {
if (!response.response) {
return;
}

this.renderedGroups.update((groups) => {
return groups.map((group, index) => (index === i ? response.response as WidgetGroup : group));
});
});
}

Expand All @@ -109,11 +120,13 @@ export class DashboardComponent implements OnInit {
});
}

// TODO: Filter out fully empty groups somewhere.
protected onSave(): void {
this.dashboardStore.save(this.renderedGroups())
.pipe(this.errorHandler.catchError(), untilDestroyed(this))
.subscribe(() => this.isEditing.set(false));
.subscribe(() => {
this.isEditing.set(false);
this.snackbar.success(this.translate.instant('Dashboard settings saved'));
});
}

private loadGroups(): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
background-color: var(--bg1);
display: flex;
flex-direction: column;
margin-bottom: 5px;
margin-left: -20px;
margin-right: -20px;
margin-top: 20px;
padding: 15px 10px;
}

.form-row {
display: flex;
gap: 8px;
gap: 20px;

ix-select,
ix-icon-group {
Expand Down
Loading

0 comments on commit f9830f2

Please sign in to comment.