Skip to content

Commit

Permalink
add tests and rename selector of MainAreaPartComponent to 'wb-part[da…
Browse files Browse the repository at this point in the history
…ta-partid="main-area"]
  • Loading branch information
danielwiehl committed Dec 16, 2024
1 parent 198ac0a commit 04f18bf
Show file tree
Hide file tree
Showing 41 changed files with 603 additions and 105 deletions.
7 changes: 0 additions & 7 deletions TODO-ACTIVITY-LAYOUT.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,3 @@


## TESTS:
- should not remove navigated part if removing last view (ɵworkbench-layout.ts:709) //__removeView
- should not null main area if part is navigated (ɵworkbench-layout.ts:430) // serialize
1. Open two parts in main area with one view each
2. Navigate right part and close view of right part
3. Close view in left part
4 expect right part to be displayed (not start page)

2 changes: 2 additions & 0 deletions apps/workbench-testing-app/src/app/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {Perspectives} from './workbench.perspectives';
import {environment} from '../environments/environment';
import {provideAnimations, provideNoopAnimations} from '@angular/platform-browser/animations';
import {provideWorkbench} from '@scion/workbench';
import {provideMainAreaInitialPartId} from './workbench/main-area-initial-part-id.provider';

/**
* Central place to configure the workbench-testing-app.
Expand All @@ -31,6 +32,7 @@ export const appConfig: ApplicationConfig = {
provideWorkbench(workbenchConfig),
provideConfirmWorkbenchStartupInitializer(),
provideThrottleCapabilityLookupInterceptor(),
provideMainAreaInitialPartId(),
provideWorkbenchLifecycleHookLoggers(),
provideDevToolsInterceptor(),
provideNotificationPage(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
<app-add-views [formControl]="form.controls.views" [partProposals]="partProposals$ | async"/>
</section>

<section>
<header>Part Navigations</header>
<app-navigate-parts [formControl]="form.controls.partNavigations" [partProposals]="partProposals$ | async"/>
</section>

<section>
<header>View Navigations</header>
<app-navigate-views [formControl]="form.controls.viewNavigations" [viewProposals]="viewProposals$ | async"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {KeyValueEntry, SciKeyValueFieldComponent} from '@scion/components.intern
import {Observable} from 'rxjs';
import {filterArray, mapArray} from '@scion/toolkit/operators';
import {AsyncPipe} from '@angular/common';
import {NavigatePartsComponent} from '../tables/navigate-parts/navigate-parts.component';

@Component({
selector: 'app-create-perspective-page',
Expand All @@ -37,6 +38,7 @@ import {AsyncPipe} from '@angular/common';
SciCheckboxComponent,
SciKeyValueFieldComponent,
AsyncPipe,
NavigatePartsComponent,
],
})
export default class CreatePerspectivePageComponent {
Expand All @@ -47,6 +49,7 @@ export default class CreatePerspectivePageComponent {
data: this._formBuilder.array<FormGroup<KeyValueEntry>>([]),
parts: this._formBuilder.control<PartDescriptor[]>([], Validators.required),
views: this._formBuilder.control<ViewDescriptor[]>([]),
partNavigations: this._formBuilder.control<NavigationDescriptor[]>([]),
viewNavigations: this._formBuilder.control<NavigationDescriptor[]>([]),
});

Expand Down Expand Up @@ -84,6 +87,7 @@ export default class CreatePerspectivePageComponent {
// Capture form values, since the `layout` function is evaluated independently of the form life-cycle
const [initialPart, ...parts] = this.form.controls.parts.value;
const views = this.form.controls.views.value;
const partNavigations = this.form.controls.partNavigations.value;
const viewNavigations = this.form.controls.viewNavigations.value;

return (factory: WorkbenchLayoutFactory): WorkbenchLayout => {
Expand Down Expand Up @@ -112,7 +116,17 @@ export default class CreatePerspectivePageComponent {
});
}

// Add navigations.
// Add part navigations.
for (const partNavigation of partNavigations) {
layout = layout.navigatePart(partNavigation.id, partNavigation.commands, {
hint: partNavigation.extras?.hint,
data: partNavigation.extras?.data,
state: partNavigation.extras?.state,
cssClass: partNavigation.extras?.cssClass,
});
}

// Add view navigations.
for (const viewNavigation of viewNavigations) {
layout = layout.navigateView(viewNavigation.id, viewNavigation.commands, {
hint: viewNavigation.extras?.hint,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
<app-add-views [formControl]="form.controls.views" [partProposals]="partProposals()"/>
</section>

<section>
<header>Part Navigations</header>
<app-navigate-parts [formControl]="form.controls.partNavigations" [partProposals]="partProposals()"/>
</section>

<section>
<header>View Navigations</header>
<app-navigate-views [formControl]="form.controls.viewNavigations" [viewProposals]="viewProposals()"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {SettingsService} from '../../settings.service';
import {WorkbenchPart, WorkbenchRouter, WorkbenchService, WorkbenchView} from '@scion/workbench';
import {stringifyError} from '../../common/stringify-error.util';
import {toSignal} from '@angular/core/rxjs-interop';
import {NavigatePartsComponent} from '../tables/navigate-parts/navigate-parts.component';

@Component({
selector: 'app-modify-layout-page',
Expand All @@ -28,13 +29,15 @@ import {toSignal} from '@angular/core/rxjs-interop';
AddViewsComponent,
NavigateViewsComponent,
ReactiveFormsModule,
NavigatePartsComponent,
],
})
export default class ModifyLayoutPageComponent {

protected form = this._formBuilder.group({
parts: this._formBuilder.control<PartDescriptor[]>([]),
views: this._formBuilder.control<ViewDescriptor[]>([]),
partNavigations: this._formBuilder.control<NavigationDescriptor[]>([]),
viewNavigations: this._formBuilder.control<NavigationDescriptor[]>([]),
});

Expand Down Expand Up @@ -105,7 +108,17 @@ export default class ModifyLayoutPageComponent {
});
}

// Add navigations.
// Add part navigations.
for (const partNavigation of this.form.controls.partNavigations.value) {
layout = layout.navigatePart(partNavigation.id, partNavigation.commands, {
hint: partNavigation.extras?.hint,
data: partNavigation.extras?.data,
state: partNavigation.extras?.state,
cssClass: partNavigation.extras?.cssClass,
});
}

// Add view navigations.
for (const viewNavigation of this.form.controls.viewNavigations.value) {
layout = layout.navigateView(viewNavigation.id, viewNavigation.commands, {
hint: viewNavigation.extras?.hint,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<form [formGroup]="form">
<span>Part ID</span>
<span>Path</span>
<span>Hint</span>
<span>Data</span>
<span>State</span>
<span>CSS Class(es)</span>
<button (click)="onAddNavigation()" class="e2e-add" title="Add navigation" sciMaterialIcon>add</button>

@for (navigationFormGroup of form.controls.navigations.controls; track $index) {
<!-- ID -->
<input [formControl]="navigationFormGroup.controls.id" class="e2e-part-id" [attr.list]="partList">
<!-- Commands -->
<app-router-commands [formControl]="navigationFormGroup.controls.commands"/>
<!-- Hint -->
<input [formControl]="navigationFormGroup.controls.extras.controls.hint" class="e2e-hint">
<!-- Data -->
<app-record [formControl]="navigationFormGroup.controls.extras.controls.data" class="e2e-data"/>
<!-- State -->
<app-record [formControl]="navigationFormGroup.controls.extras.controls.state" class="e2e-state"/>
<!-- CSS class -->
<app-css-class [formControl]="navigationFormGroup.controls.extras.controls.cssClass"/>
<!-- Remove button -->
<button class="e2e-remove" (click)="onRemoveNavigation($index)" title="Remove navigation" sciMaterialIcon>remove</button>
}
</form>

<datalist [attr.id]="partList">
@for (part of partProposals; track part) {
<option [value]="part">{{part}}</option>
}
</datalist>
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
@use '@scion/components.internal/design' as sci-design;

:host {
display: grid;

> form {
display: grid;
grid-template-columns: 7.5em 1fr 10em 14em 14em 10em auto;
gap: .5em .75em;
align-items: center;

> input {
@include sci-design.style-input-field();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/*
* Copyright (c) 2018-2024 Swiss Federal Railways
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/

import {Component, forwardRef, Input} from '@angular/core';
import {noop} from 'rxjs';
import {AbstractControl, ControlValueAccessor, FormControl, FormGroup, NG_VALIDATORS, NG_VALUE_ACCESSOR, NonNullableFormBuilder, ReactiveFormsModule, ValidationErrors, Validator, Validators} from '@angular/forms';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {SciMaterialIconDirective} from '@scion/components.internal/material-icon';
import {Commands, NavigationData, NavigationState} from '@scion/workbench';
import {RouterCommandsComponent} from '../../../router-commands/router-commands.component';
import {CssClassComponent} from '../../../css-class/css-class.component';
import {UUID} from '@scion/toolkit/uuid';
import {RecordComponent} from '../../../record/record.component';

@Component({
selector: 'app-navigate-parts',
templateUrl: './navigate-parts.component.html',
styleUrls: ['./navigate-parts.component.scss'],
standalone: true,
imports: [
ReactiveFormsModule,
SciMaterialIconDirective,
RouterCommandsComponent,
RecordComponent,
CssClassComponent,
],
providers: [
{provide: NG_VALUE_ACCESSOR, multi: true, useExisting: forwardRef(() => NavigatePartsComponent)},
{provide: NG_VALIDATORS, multi: true, useExisting: forwardRef(() => NavigatePartsComponent)},
],
})
export class NavigatePartsComponent implements ControlValueAccessor, Validator {

private _cvaChangeFn: (value: NavigationDescriptor[]) => void = noop;
private _cvaTouchedFn: () => void = noop;

@Input({transform: arrayAttribute})
public partProposals: string[] = [];

protected form = this._formBuilder.group({
navigations: this._formBuilder.array<FormGroup<{
id: FormControl<string>;
commands: FormControl<Commands>;
extras: FormGroup<{
hint: FormControl<string | undefined>;
data: FormControl<NavigationData | undefined>;
state: FormControl<NavigationState | undefined>;
cssClass: FormControl<string | string[] | undefined>;
}>;
}>>([]),
});
protected partList = `part-list-${UUID.randomUUID()}`;

constructor(private _formBuilder: NonNullableFormBuilder) {
this.form.valueChanges
.pipe(takeUntilDestroyed())
.subscribe(() => {
this._cvaChangeFn(this.form.controls.navigations.controls.map(navigationFormGroup => ({
id: navigationFormGroup.controls.id.value,
commands: navigationFormGroup.controls.commands.value,
extras: ({
hint: navigationFormGroup.controls.extras.controls.hint.value || undefined,
data: navigationFormGroup.controls.extras.controls.data.value,
state: navigationFormGroup.controls.extras.controls.state.value,
cssClass: navigationFormGroup.controls.extras.controls.cssClass.value,
}),
})));
this._cvaTouchedFn();
});
}

protected onAddNavigation(): void {
this.addNavigation({
id: '',
commands: [],
});
}

protected onRemoveNavigation(index: number): void {
this.form.controls.navigations.removeAt(index);
}

private addNavigation(navigation: NavigationDescriptor, options?: {emitEvent?: boolean}): void {
this.form.controls.navigations.push(
this._formBuilder.group({
id: this._formBuilder.control<string>(navigation.id, Validators.required),
commands: this._formBuilder.control<Commands>(navigation.commands),
extras: this._formBuilder.group({
hint: this._formBuilder.control<string | undefined>(navigation.extras?.hint),
data: this._formBuilder.control<NavigationData | undefined>(navigation.extras?.data),
state: this._formBuilder.control<NavigationState | undefined>(navigation.extras?.state),
cssClass: this._formBuilder.control<string | string[] | undefined>(undefined),
}),
}), {emitEvent: options?.emitEvent ?? true});
}

/**
* Method implemented as part of `ControlValueAccessor` to work with Angular forms API
* @docs-private
*/
public writeValue(navigations: NavigationDescriptor[] | undefined | null): void {
this.form.controls.navigations.clear({emitEvent: false});
navigations?.forEach(navigation => this.addNavigation(navigation, {emitEvent: false}));
}

/**
* Method implemented as part of `ControlValueAccessor` to work with Angular forms API
* @docs-private
*/
public registerOnChange(fn: any): void {
this._cvaChangeFn = fn;
}

/**
* Method implemented as part of `ControlValueAccessor` to work with Angular forms API
* @docs-private
*/
public registerOnTouched(fn: any): void {
this._cvaTouchedFn = fn;
}

/**
* Method implemented as part of `Validator` to work with Angular forms API
* @docs-private
*/
public validate(control: AbstractControl): ValidationErrors | null {
return this.form.controls.navigations.valid ? null : {valid: false};
}
}

export interface NavigationDescriptor {
id: string;
commands: Commands;
extras?: {
hint?: string;
data?: NavigationData;
state?: NavigationState;
cssClass?: string | string[];
};
}

function arrayAttribute(proposals: string[] | null | undefined): string[] {
return proposals ?? [];
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
<input placeholder="Part ID" #part_id>
<button (click)="onNavigatePart(part_id.value)">Navigate Part</button>
<form autocomplete="off" [formGroup]="form">
<section>
<header>Routing Command</header>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,6 @@ export default class RouterPageComponent {
this.resetForm();
}

protected onNavigatePart(partId: string): void {
this._wbRouter.navigate(layout => layout.navigatePart(partId, ['test-part']));
}

private readExtrasFromUI(): WorkbenchNavigationExtras {
const extras = this.form.controls.extras.controls;
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
}

// Hide reload button if in the main area and the tabbar is showing.
&.main-area {
wb-part[data-partid="main-area"] & {
@container view (min-width: 450px) {
> button.reload {
display: none;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* SPDX-License-Identifier: EPL-2.0
*/

import {Component, effect, HostBinding, inject, signal, untracked} from '@angular/core';
import {Component, effect, inject, signal, untracked} from '@angular/core';
import {TableSkeletonComponent} from '../skeletons/table-sekeleton/table-skeleton.component';
import {InputFieldSkeletonComponent} from '../skeletons/input-field-sekeleton/input-field-skeleton.component';
import {TabbarSkeletonComponent} from '../skeletons/tabbar-sekeleton/tabbar-skeleton.component';
Expand Down Expand Up @@ -53,11 +53,6 @@ export default class SampleViewComponent {
protected selectedTab = signal(Skeletons.random(0, this.tabs.length - 1));
protected view = inject(WorkbenchView);

@HostBinding('class.main-area')
protected get isInMainArea(): boolean {
return this.view.part().isInMainArea;
}

constructor() {
effect(() => {
const navigationData = this.view.navigationData() as SkeletonNavigationData;
Expand Down
Loading

0 comments on commit 04f18bf

Please sign in to comment.