Compare commits

..
Author SHA1 Message Date
Dawid L Jablonski 124ed9ce69 [ci:force] 2023-03-31 12:54:23 +02:00
Dawid L Jablonski d2cf66cb5d [ci:force] 2023-03-31 12:42:00 +02:00
Dawid L Jablonski 0a4a0679c9 Corrected condition 2023-03-31 12:28:31 +02:00
Dawid L Jablonski c26ee4f1aa [ci:force] 2023-03-31 10:17:08 +02:00
Dawid L Jablonski 6c55cc50bd [ci:force] 2023-03-31 10:14:27 +02:00
Dawid L Jablonski 37e067cc56 [ci:force] 2023-03-30 17:32:19 +02:00
Dawid L Jablonski 9755933b46 [ci:force] 2023-03-30 16:52:09 +02:00
Dawid L Jablonski e701daad77 [ci:force] 2023-03-30 15:25:13 +02:00
Dawid L Jablonski 720280d612 [ci:force] 2023-03-30 15:01:38 +02:00
Dawid L Jablonski 1d555dfcd1 [ci:force] 2023-03-30 14:34:15 +02:00
Dawid L Jablonski 1440455eb9 Corrected one line 2023-03-30 12:52:56 +02:00
Dawid L Jablonski a3019b1602 Corrected conditions on ng2' 2023-03-30 12:07:29 +02:00
101 changed files with 1384 additions and 4250 deletions
+2 -8
View File
@@ -38,10 +38,6 @@ inputs:
apa-proxy: #
description: "proxy host"
required: true
deps:
description: "Library dependencies"
required: false
default: ""
runs:
using: "composite"
@@ -125,7 +121,6 @@ runs:
PROVIDER: "${{ inputs.e2e-test-provider }}"
AUTH_TYPE: "${{ inputs.e2e-test-auth }}"
E2E_TEST_ID: "${{ inputs.e2e-test-id }}"
DEPS: "${{ inputs.deps }}"
shell: bash
run: |
set -u;
@@ -139,7 +134,6 @@ runs:
PROVIDER: "${{ inputs.e2e-test-provider }}"
AUTH_TYPE: "${{ inputs.e2e-test-auth }}"
E2E_TEST_ID: "${{ inputs.e2e-test-id }}"
DEPS: "${{ inputs.deps }}"
uses: nick-fields/retry@v2.8.2
with:
@@ -155,9 +149,9 @@ runs:
export DISPLAY=:99
chromedriver --url-base=/wd/hub &
sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & # optional
bash ./scripts/github/e2e/e2e.sh "$E2E_TEST_ID" "$DEPS" "browser" || exit 1
bash ./scripts/github/e2e/e2e.sh "$E2E_TEST_ID" "browser" || exit 1
else
bash ./scripts/github/e2e/e2e.sh "$E2E_TEST_ID" "$DEPS" || exit 1
bash ./scripts/github/e2e/e2e.sh "$E2E_TEST_ID" || exit 1
fi
- name: upload artifacts on gh
+7
View File
@@ -18,3 +18,10 @@ jobs:
secrets: inherit
with:
repo_to_update: alfresco-applications
adf-upstream-alpha-hxp:
name: trigger upstream alpha for hxp monorepo
uses: ./.github/workflows/adf_alpha.yml
secrets: inherit
with:
repo_to_update: alfresco-apps
+3 -20
View File
@@ -110,7 +110,7 @@ jobs:
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Get branch name
uses: Alfresco/alfresco-build-tools/.github/actions/get-branch-name@v1.36.0
@@ -191,7 +191,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
with:
fetch-depth: 0 # Fetch all history for all tags and branches
- uses: ./.github/actions/setup
- name: install
@@ -258,7 +258,7 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 0 # Fetch all history for all tags and branches
fetch-depth: 0 # Fetch all history for all tags and branches
- uses: ./.github/actions/setup
- uses: ./.github/actions/download-node-modules-and-artifacts
- run: nx affected:build $NX_CALCULATION_FLAGS --prod
@@ -302,49 +302,42 @@ jobs:
auth: "OAUTH"
check-cs-env: "true"
check-ps-env: "true"
deps: "testing"
- description: "Content: Components"
test-id: "content-services"
folder: "content-services/components"
provider: "ECM"
auth: "BASIC"
check-cs-env: "true"
deps: "testing"
- description: "Content: Directives"
test-id: "content-services"
folder: "content-services/directives"
provider: "ECM"
auth: "BASIC"
check-cs-env: "true"
deps: "testing"
- description: "Content: Document List"
test-id: "content-services"
folder: "content-services/document-list"
provider: "ECM"
auth: "BASIC"
check-cs-env: "true"
deps: "testing"
- description: "Content: Metadata"
test-id: "content-services"
folder: "content-services/metadata"
provider: "ECM"
auth: "BASIC"
check-cs-env: "true"
deps: "testing"
- description: "Content: Upload and Versioning"
test-id: "content-services"
folder: "content-services/upload"
provider: "ECM"
auth: "BASIC"
check-cs-env: "true"
deps: "testing"
- description: "Search"
test-id: "content-services"
folder: "search"
provider: "ECM"
auth: "BASIC"
check-cs-env: "true"
deps: "testing"
- description: "Process: Form"
test-id: "process-services"
folder: "process-services/form"
@@ -352,7 +345,6 @@ jobs:
auth: "OAUTH"
check-ps-env: "true"
check-external-cs-env: "true"
deps: "testing"
- description: "Process: Process"
test-id: "process-services"
folder: "process-services/process"
@@ -360,7 +352,6 @@ jobs:
auth: "OAUTH"
check-ps-env: "true"
check-external-cs-env: "true"
deps: "testing"
- description: "Process: Tasks"
test-id: "process-services"
folder: "process-services/tasks"
@@ -368,7 +359,6 @@ jobs:
auth: "OAUTH"
check-ps-env: "true"
check-external-cs-env: "true"
deps: "testing"
- description: "Process: Widget"
test-id: "process-services"
folder: "process-services/widgets"
@@ -376,7 +366,6 @@ jobs:
auth: "OAUTH"
check-ps-env: "true"
check-external-cs-env: "true"
deps: "testing"
- description: "Process Cloud: Form"
test-id: "process-services-cloud"
folder: "process-services-cloud/form-field"
@@ -385,7 +374,6 @@ jobs:
apa-proxy: true
check-cs-env: "true"
check-ps-cloud-env: "true"
deps: "testing"
- description: "Process Cloud: People"
test-id: "process-services-cloud"
folder: "process-services-cloud/people"
@@ -394,7 +382,6 @@ jobs:
apa-proxy: true
check-cs-env: "true"
check-ps-cloud-env: "true"
deps: "testing"
- description: "Process Cloud: Process"
test-id: "process-services-cloud"
folder: "process-services-cloud/process"
@@ -403,7 +390,6 @@ jobs:
apa-proxy: true
check-cs-env: "true"
check-ps-cloud-env: "true"
deps: "testing"
- description: "Process Cloud: Start Task"
test-id: "process-services-cloud"
folder: "process-services-cloud/start-task"
@@ -412,7 +398,6 @@ jobs:
apa-proxy: true
check-cs-env: "true"
check-ps-cloud-env: "true"
deps: "testing"
- description: "Process Cloud: Tasks List"
test-id: "process-services-cloud"
folder: "process-services-cloud/task-list"
@@ -421,7 +406,6 @@ jobs:
apa-proxy: true
check-cs-env: "true"
check-ps-cloud-env: "true"
deps: "testing"
steps:
- name: Checkout repository
@@ -447,7 +431,6 @@ jobs:
check-ps-cloud-env: ${{ matrix.e2e-test.check-ps-cloud-env }}
check-external-cs-env: ${{ matrix.e2e-test.check-external-cs-env }}
apa-proxy: ${{ matrix.e2e-test.apa-proxy }}
deps: ${{ matrix.e2e-test.deps }}
finalize:
if: ${{ always() }}
View File
-4
View File
@@ -1476,9 +1476,5 @@
"ai:labels",
"ai:textLines"
]
},
"viewer": {
"enableFileAutoDownload": true,
"fileAutoDownloadSizeThresholdInMB": 15
}
}
@@ -653,18 +653,6 @@
</mat-form-field>
</form>
<section>
<mat-slide-toggle
color="primary" [(ngModel)]="enableFileAutoDownload" id="enableFileAutoDownload" (change)="onEnableFileAutoDownloadChange()">
Enable FileAutoDownload
</mat-slide-toggle>
</section>
<section *ngIf="enableFileAutoDownload">
<mat-form-field>
<input matInput type="number" [(ngModel)]="fileAutoDownloadSizeThresholdInMB" (change)="onFileAutoDownloadSizeThresholdChange()">
</mat-form-field>
</section>
<h5>Upload</h5>
<section *ngIf="acceptedFilesTypeShow">
<mat-form-field floatPlaceholder="float">
@@ -251,9 +251,6 @@ export class FilesComponent implements OnInit, OnChanges, OnDestroy {
selectedNodes = [];
enableFileAutoDownload: boolean = this.appConfig.get('viewer.enableFileAutoDownload', true);
fileAutoDownloadSizeThresholdInMB: number = this.appConfig.get('viewer.fileAutoDownloadSizeThresholdInMB', 15);
constructor(private notificationService: NotificationService,
private uploadService: UploadService,
private contentService: ContentService,
@@ -780,14 +777,4 @@ export class FilesComponent implements OnInit, OnChanges, OnDestroy {
this.selectedNodes = [];
}
onEnableFileAutoDownloadChange() {
const previewConfig = this.appConfig?.config['viewer'];
previewConfig['enableFileAutoDownload'] = this.enableFileAutoDownload;
}
onFileAutoDownloadSizeThresholdChange() {
const previewConfig = this.appConfig?.config['viewer'];
previewConfig['fileAutoDownloadSizeThresholdInMB'] = this.fileAutoDownloadSizeThresholdInMB;
}
}
@@ -790,27 +790,6 @@ This will give the following output:
![Custom loading](../../docassets/images/custom-loading.png)
### File Auto downloading
In case of files exceeding a predefined file size, the document list component can be configured to automatically download those file when trying to preview them.
This can help in reducing server load, and ensuring quick access to such files. After turning this feature on, whenever the user tries to preview a file with a large
file size, the Document List component will first preview a dialog, asking for confirmation from the user on whether they want to download the file, or cancel the preview altogether.
In order to configure the Document List to automatically download the files, the following environment variables would need to be set up in app.config.json -
```
"viewer": {
"enableFileAutoDownload": true,
"fileAutoDownloadSizeThresholdInMB": 15
}
```
Here, `"enableFileAutoDownload": true,` would enable the file auto download feature on the Document List component. Setting this flag to false disables this feature, and always
triggers a file preview when trying to view a file, regardless of its size.
The second configuration here, `"fileAutoDownloadSizeThresholdInMB": 15` specifies the file size threshold (in MB), after which the Document List component will start downloading the file.
In the example provided above, any file greater than 15MB in size would trigger the auto download functionality. Files lower than 15MB in size would continue to preview normally.
## See also
- [Datatable component](../../core/components/datatable.component.md)
@@ -1,40 +0,0 @@
---
Title: Tags Creator component
Added: v6.0.0-A.3
Status: Active
Last reviewed: 2023-03-27
---
# [Tags Creator component](../../../lib/content-services/src/lib/tag/tags-creator/tags-creator.component.ts "Defined in tags-creator.component.ts")
Allows to create multiple tags. That component contains input and two lists. Top list is all created tags, bottom list is searched tags based on input's value.
## Basic Usage
```html
<adf-tags-creator
[(tagNameControlVisible)]="tagNameControlVisible"
(tagsChange)="tags = $event"
[mode]="tagsCreatorMode">
</adf-tags-creator>
```
## Class members
### Properties
| Name | Type | Default value | Description |
|-----------------------|-------------------|---------------|-------------------------------------------------------------------------------------------------------------------------------------|
| mode | `TagsCreatorMode` | | Create mode if only new tags can be created or Create And Assign mode if new tags can be created and existing tags can be selected. |
| disabledTagsRemoving | `boolean` | false | False if tags can be removed from top list, true otherwise. |
| tags | `string[]` | | Default top list. |
| tagNameControlVisible | `boolean` | false | True if input should be visible, false otherwise. | |
### Events
| Name | Type | Description |
|-----------------------------------|------------------------------------------------------------------------|--------------------------------------------------|
| existingTagsPanelVisibilityChange | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<boolean>` | Emitted when bottom list is showing or hiding. |
| tagsChange | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<string[]>` | Emitted when tags in top list are changed. |
| tagNameControlVisibleChange | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<boolean>` | Emitted when input is showing or hiding. |
@@ -61,11 +61,6 @@ Manages tags in Content Services.
Find tag which name matches exactly to passed name.
- _name:_ `string` - Value for name which should be used during finding exact tag.
- **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<TagEntry>` - Found tag which name matches exactly to passed name.
- **assignTagsToNode**(nodeId: `string`, tags: `TagBody[]`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`TagPaging`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/TagPaging.md)`|`[`TagEntry`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/TagEntry.md)`>`<br/>
Assign tags to node. If tag is new then tag is also created additionally, if tag already exists then it is just assigned.
- _nodeId:_ `string` - Id of node to which tags should be assigned.
- _tags:_ `TagBody[]` - List of tags to create and assign or just assign if they already exist.
- **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`TagPaging`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/TagPaging.md)`|`[`TagEntry`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/TagEntry.md)`>` - Just linked tags to node or single tag if linked only one tag.
## Details
@@ -2,7 +2,7 @@
Title: Edit Process Filter Cloud component
Added: v3.0.0
Status: Experimental
Last reviewed: 2023-04-03
Last reviewed: 2019-04-10
---
# [Edit Process Filter Cloud component](../../../lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.ts "Defined in edit-process-filter-cloud.component.ts")
@@ -47,8 +47,6 @@ Shows/edits process filter details.
| appName | `string` | "" | The name of the application. |
| filterProperties | `string[]` | | List of process filter properties to display |
| id | `string` | | Id of the process instance filter. |
| environmentId | `string` | | Environment Id of the process instance filter. |
| environmentList | `string[]` | | List of environments. |
| role | `string` | "" | roles to filter the apps |
| showFilterActions | `boolean` | true | Toggles editing of process filter actions. |
| showProcessFilterName | `boolean` | true | Toggles the appearance of the process filter name . |
@@ -2,7 +2,7 @@
Title: Edit Task Filter Cloud component
Added: v3.0.0
Status: Experimental
Last reviewed: 2023-04-03
Last reviewed: 2019-03-27
---
# [Edit Task Filter Cloud component](../../../lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filters/edit-task-filter-cloud.component.ts "Defined in edit-task-filter-cloud.component.ts")
@@ -47,8 +47,6 @@ Edits task filter details.
| appName | `string` | "" | (required) Name of the app. |
| filterProperties | `string[]` | \[] | List of task filter properties to display. |
| id | `string` | | (required) ID of the task filter. |
| environmentId | `string` | | Environment Id of the task filter. |
| environmentList | `string[]` | | List of environments. |
| processInstanceId | `string` | | processInstanceId of the task filter. |
| role | `string` | "" | user role. |
| showFilterActions | `boolean` | true | Toggles the filter actions. |
@@ -2,7 +2,7 @@
Title: Apps Process Cloud Service
Added: v3.0.0
Status: Experimental
Last reviewed: 2023-04-03
Last reviewed: 2019-01-09
---
# [Apps Process Cloud Service](../../../lib/process-services-cloud/src/lib/app/services/apps-process-cloud.service.ts "Defined in apps-process-cloud.service.ts")
@@ -24,11 +24,6 @@ Gets details of deployed apps for the current user.
- **loadApps**()<br/>
- **getApplicationLabel**(application: `ApplicationInstanceModel`, environmentList?: `Environment[]`): `string`<br/>
Gets application label.
- application:_ `ApplicationInstanceModel` - Required application
- environmentList:_ `Environment[]` - (Optional) to add environment name in front of application name
- **Returns** `string` - The application label.
## Details
This service implements some features of the [Apps process service](../../core/services/apps-process.service.md)
@@ -25,7 +25,6 @@ import { ContentTestingModule } from '../../testing/content.testing.module';
import { NodeAspectService } from './node-aspect.service';
import { DialogAspectListService } from './dialog-aspect-list.service';
import { CardViewContentUpdateService } from '../../common/services/card-view-content-update.service';
import { TagService } from '@alfresco/adf-content-services';
describe('NodeAspectService', () => {
@@ -100,13 +99,4 @@ describe('NodeAspectService', () => {
nodeAspectService.updateNodeAspects('fake-node-id');
});
it('should call emit on refresh from TagService', () => {
const tagService = TestBed.inject(TagService);
spyOn(dialogAspectListService, 'openAspectListDialog').and.returnValue(of([]));
const node = new MinimalNode({ id: 'fake-node-id', aspectNames: ['a', 'b', 'c'] });
spyOn(nodeApiService, 'updateNode').and.returnValue(of(node));
spyOn(tagService.refresh, 'emit');
nodeAspectService.updateNodeAspects('some node id', 'some-selector');
expect(tagService.refresh.emit).toHaveBeenCalled();
});
});
@@ -19,7 +19,6 @@ import { Injectable } from '@angular/core';
import { DialogAspectListService } from './dialog-aspect-list.service';
import { CardViewContentUpdateService } from '../../common/services/card-view-content-update.service';
import { NodesApiService } from '../../common/services/nodes-api.service';
import { TagService } from '../../tag/services/tag.service';
@Injectable({
providedIn: 'root'
@@ -28,8 +27,7 @@ export class NodeAspectService {
constructor(private nodesApiService: NodesApiService,
private dialogAspectListService: DialogAspectListService,
private cardViewContentUpdateService: CardViewContentUpdateService,
private tagService: TagService) {
private cardViewContentUpdateService: CardViewContentUpdateService) {
}
updateNodeAspects(nodeId: string, selectorAutoFocusedOnClose?: string) {
@@ -37,7 +35,6 @@ export class NodeAspectService {
this.nodesApiService.updateNode(nodeId, { aspectNames: [...aspectList] }).subscribe((updatedNode) => {
this.nodesApiService.nodeUpdated.next(updatedNode);
this.cardViewContentUpdateService.updateNodeAspect(updatedNode);
this.tagService.refresh.emit();
});
});
}
@@ -8,8 +8,7 @@
[editable]="editable"
[multi]="multi"
[displayAspect]="displayAspect"
[preset]="preset"
[displayTags]="true">
[preset]="preset">
</adf-content-metadata>
</mat-card-content>
<mat-card-footer class="adf-content-metadata-card-footer" fxLayout="row" fxLayoutAlign="space-between stretch">
@@ -55,8 +55,7 @@ describe('ContentMetadataCardComponent', () => {
content: {},
properties: {},
createdByUser: {},
modifiedByUser: {},
id: 'some-id'
modifiedByUser: {}
} as Node;
component.node = node;
@@ -92,10 +91,6 @@ describe('ContentMetadataCardComponent', () => {
expect(contentMetadataComponent.node).toBe(node);
});
it('should assign true to displayTags for ContentMetadataComponent', () => {
expect(fixture.debugElement.query(By.directive(ContentMetadataComponent)).componentInstance.displayTags).toBeTrue();
});
it('should pass through the preset to the underlying component', () => {
const contentMetadataComponent = fixture.debugElement.query(By.directive(ContentMetadataComponent)).componentInstance;
@@ -19,36 +19,7 @@
[multiValueSeparator]="multiValueSeparator">
</adf-card-view>
</mat-expansion-panel>
<ng-container *ngIf="displayTags">
<mat-expansion-panel *ngIf="!editable">
<mat-expansion-panel-header>
<mat-panel-title>{{ 'METADATA.BASIC.TAGS' | translate }}</mat-panel-title>
</mat-expansion-panel-header>
<p *ngFor="let tag of tags" class="adf-metadata-properties-tag">{{ tag }}</p>
</mat-expansion-panel>
<div
*ngIf="editable"
class="adf-metadata-properties-tags">
<div class="adf-metadata-properties-tags-title">
<p>{{ 'METADATA.BASIC.TAGS' | translate }}</p>
<button
data-automation-id="showing-tag-input-button"
mat-icon-button
[attr.title]="'METADATA.BASIC.ADD_TAG_TOOLTIP' | translate"
(click)="tagNameControlVisible = true"
[hidden]="tagNameControlVisible || saving">
<mat-icon>add</mat-icon>
</button>
</div>
<adf-tags-creator
[(tagNameControlVisible)]="tagNameControlVisible"
(tagsChange)="storeTagsToAssign($event)"
[mode]="tagsCreatorMode"
[tags]="assignedTags"
[disabledTagsRemoving]="saving">
</adf-tags-creator>
</div>
</ng-container>
<ng-container *ngIf="expanded">
<ng-container *ngIf="groupedProperties$ | async; else loading; let groupedProperties">
<div *ngFor="let group of groupedProperties; let first = first;"
@@ -17,45 +17,6 @@
.mat-expansion-panel:not([class*='mat-elevation-z']) {
box-shadow: none;
}
.adf-metadata-properties-tag {
height: 40px;
display: flex;
align-items: center;
margin-top: -14px;
margin-bottom: 1em;
}
&-tags {
padding: 0 10px 0 26px;
&-title {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 15px;
height: 68px;
}
adf-tags-creator {
.adf-tags-creation {
padding-right: 0;
padding-left: 12px;
.adf-tag {
margin-top: -14px;
}
}
&.adf-creator-with-existing-tags-panel {
background: var(--adf-theme-background-dialog-color);
}
}
[hidden] {
visibility: hidden;
}
}
}
&-metadata-action-buttons {
@@ -16,24 +16,23 @@
*/
import { ComponentFixture, TestBed, tick, fakeAsync } from '@angular/core/testing';
import { DebugElement, SimpleChange } from '@angular/core';
import { SimpleChange } from '@angular/core';
import { By } from '@angular/platform-browser';
import { ClassesApi, MinimalNode, Node, Tag, TagBody, TagEntry, TagPaging, TagPagingList } from '@alfresco/js-api';
import { ClassesApi, MinimalNode, Node } from '@alfresco/js-api';
import { ContentMetadataComponent } from './content-metadata.component';
import { ContentMetadataService } from '../../services/content-metadata.service';
import {
CardViewBaseItemModel, CardViewComponent,
LogService, setupTestBed, AppConfigService, UpdateNotification
LogService, setupTestBed, AppConfigService
} from '@alfresco/adf-core';
import { NodesApiService } from '../../../common/services/nodes-api.service';
import { throwError, of, EMPTY } from 'rxjs';
import { throwError, of } from 'rxjs';
import { ContentTestingModule } from '../../../testing/content.testing.module';
import { mockGroupProperties } from './mock-data';
import { TranslateModule } from '@ngx-translate/core';
import { CardViewContentUpdateService } from '../../../common/services/card-view-content-update.service';
import { PropertyGroup } from '../../interfaces/property-group.interface';
import { PropertyDescriptorsService } from '../../services/property-descriptors.service';
import { TagsCreatorComponent, TagsCreatorMode, TagService } from '@alfresco/adf-content-services';
describe('ContentMetadataComponent', () => {
let component: ContentMetadataComponent;
@@ -43,64 +42,14 @@ describe('ContentMetadataComponent', () => {
let nodesApiService: NodesApiService;
let node: Node;
let folderNode: Node;
let tagService: TagService;
const preset = 'custom-preset';
const mockTagPaging = (): TagPaging => {
const tagPaging = new TagPaging();
tagPaging.list = new TagPagingList();
const tagEntry1 = new TagEntry();
tagEntry1.entry = new Tag();
tagEntry1.entry.tag = 'Tag 1';
tagEntry1.entry.id = 'some id 1';
const tagEntry2 = new TagEntry();
tagEntry2.entry = new Tag();
tagEntry2.entry.tag = 'Tag 2';
tagEntry2.entry.id = 'some id 2';
tagPaging.list.entries = [tagEntry1, tagEntry2];
return tagPaging;
};
const findTagElements = (): DebugElement[] => fixture.debugElement.queryAll(By.css('.adf-metadata-properties-tag'));
const findCancelButton = (): HTMLButtonElement => fixture.debugElement.query(By.css('[data-automation-id=reset-metadata]')).nativeElement;
const clickOnCancel = () => {
findCancelButton().click();
fixture.detectChanges();
};
const findSaveButton = (): HTMLButtonElement => fixture.debugElement.query(By.css('[data-automation-id=save-metadata]')).nativeElement;
const clickOnSave = () => {
findSaveButton().click();
fixture.detectChanges();
};
const findTagsCreator = (): TagsCreatorComponent => fixture.debugElement.query(By.directive(TagsCreatorComponent))?.componentInstance;
const findShowingTagInputButton = (): HTMLButtonElement =>
fixture.debugElement.query(By.css('[data-automation-id=showing-tag-input-button]')).nativeElement;
setupTestBed({
imports: [
TranslateModule.forRoot(),
ContentTestingModule
],
providers: [{
provide: LogService,
useValue: {
error: jasmine.createSpy('error')
}
}, {
provide: TagService,
useValue: {
getTagsByNodeId: () => EMPTY,
removeTag: () => EMPTY,
assignTagsToNode: () => EMPTY
}
}]
providers: [{ provide: LogService, useValue: { error: jasmine.createSpy('error') } }]
});
beforeEach(() => {
@@ -109,7 +58,6 @@ describe('ContentMetadataComponent', () => {
contentMetadataService = TestBed.inject(ContentMetadataService);
updateService = TestBed.inject(CardViewContentUpdateService);
nodesApiService = TestBed.inject(NodesApiService);
tagService = TestBed.inject(TagService);
node = {
id: 'node-id',
@@ -203,65 +151,14 @@ describe('ContentMetadataComponent', () => {
fixture.detectChanges();
await fixture.whenStable();
clickOnSave();
const saveButton = fixture.debugElement.query(By.css('[data-automation-id="save-metadata"]'));
saveButton.nativeElement.click();
await fixture.whenStable();
expect(component.node).toEqual(expectedNode);
expect(nodesApiService.updateNode).toHaveBeenCalled();
}));
it('should call removeTag and assignTagsToNode on TagService on save click', fakeAsync( () => {
component.editable = true;
component.displayTags = true;
const property = { key: 'properties.property-key', value: 'original-value' } as CardViewBaseItemModel;
const expectedNode = { ...node, name: 'some-modified-value' };
spyOn(nodesApiService, 'updateNode').and.returnValue(of(expectedNode));
const tagPaging = mockTagPaging();
spyOn(tagService, 'getTagsByNodeId').and.returnValue(of(tagPaging));
component.ngOnInit();
spyOn(tagService, 'removeTag').and.returnValue(EMPTY);
spyOn(tagService, 'assignTagsToNode').and.returnValue(EMPTY);
const tagName1 = tagPaging.list.entries[0].entry.tag;
const tagName2 = 'New tag 3';
updateService.update(property, 'updated-value');
tick(600);
fixture.detectChanges();
findTagsCreator().tagsChange.emit([tagName1, tagName2]);
clickOnSave();
const tag1 = new TagBody();
tag1.tag = tagName1;
const tag2 = new TagBody();
tag2.tag = tagName2;
expect(tagService.removeTag).toHaveBeenCalledWith(node.id, tagPaging.list.entries[1].entry.id);
expect(tagService.assignTagsToNode).toHaveBeenCalledWith(node.id, [tag1, tag2]);
}));
it('should call getTagsByNodeId on TagService on save click', fakeAsync( () => {
component.editable = true;
component.displayTags = true;
const property = { key: 'properties.property-key', value: 'original-value' } as CardViewBaseItemModel;
const expectedNode = { ...node, name: 'some-modified-value' };
spyOn(nodesApiService, 'updateNode').and.returnValue(of(expectedNode));
const tagPaging = mockTagPaging();
const getTagsByNodeIdSpy = spyOn(tagService, 'getTagsByNodeId').and.returnValue(of(tagPaging));
component.ngOnInit();
spyOn(tagService, 'removeTag').and.returnValue(of(undefined));
spyOn(tagService, 'assignTagsToNode').and.returnValue(of({}));
updateService.update(property, 'updated-value');
tick(600);
fixture.detectChanges();
findTagsCreator().tagsChange.emit([tagPaging.list.entries[0].entry.tag, 'New tag 3']);
getTagsByNodeIdSpy.calls.reset();
clickOnSave();
expect(tagService.getTagsByNodeId).toHaveBeenCalledWith(node.id);
}));
it('should throw error on unsuccessful save', fakeAsync((done) => {
const logService: LogService = TestBed.inject(LogService);
component.editable = true;
@@ -280,7 +177,11 @@ describe('ContentMetadataComponent', () => {
spyOn(nodesApiService, 'updateNode').and.returnValue(throwError(new Error('My bad')));
fixture.detectChanges();
fixture.whenStable().then(() => clickOnSave());
fixture.whenStable().then(() => {
const saveButton = fixture.debugElement.query(By.css('[data-automation-id="save-metadata"]'));
saveButton.nativeElement.click();
fixture.detectChanges();
});
}));
it('should open the confirm dialog when content type is changed', fakeAsync(() => {
@@ -295,7 +196,8 @@ describe('ContentMetadataComponent', () => {
fixture.detectChanges();
tick(100);
clickOnSave();
const saveButton = fixture.debugElement.query(By.css('[data-automation-id="save-metadata"]'));
saveButton.nativeElement.click();
tick(100);
expect(component.node).toEqual(expectedNode);
@@ -303,39 +205,6 @@ describe('ContentMetadataComponent', () => {
expect(nodesApiService.updateNode).toHaveBeenCalled();
}));
it('should call removeTag and assignTagsToNode on TagService after confirming confirmation dialog when content type is changed', fakeAsync(() => {
component.editable = true;
component.displayTags = true;
const property = { key: 'nodeType', value: 'ft:sbiruli' } as CardViewBaseItemModel;
const expectedNode = { ...node, nodeType: 'ft:sbiruli' };
spyOn(contentMetadataService, 'openConfirmDialog').and.returnValue(of(true));
spyOn(nodesApiService, 'updateNode').and.returnValue(of(expectedNode));
const tagPaging = mockTagPaging();
spyOn(tagService, 'getTagsByNodeId').and.returnValue(of(tagPaging));
component.ngOnInit();
spyOn(tagService, 'removeTag').and.returnValue(EMPTY);
spyOn(tagService, 'assignTagsToNode').and.returnValue(EMPTY);
const tagName1 = tagPaging.list.entries[0].entry.tag;
const tagName2 = 'New tag 3';
updateService.update(property, 'ft:poppoli');
tick(600);
fixture.detectChanges();
findTagsCreator().tagsChange.emit([tagName1, tagName2]);
tick(100);
fixture.detectChanges();
clickOnSave();
tick(100);
const tag1 = new TagBody();
tag1.tag = tagName1;
const tag2 = new TagBody();
tag2.tag = tagName2;
expect(tagService.removeTag).toHaveBeenCalledWith(node.id, tagPaging.list.entries[1].entry.id);
expect(tagService.assignTagsToNode).toHaveBeenCalledWith(node.id, [tag1, tag2]);
}));
it('should retrigger the load of the properties when the content type has changed', fakeAsync(() => {
component.editable = true;
const property = { key: 'nodeType', value: 'ft:sbiruli' } as CardViewBaseItemModel;
@@ -349,7 +218,8 @@ describe('ContentMetadataComponent', () => {
fixture.detectChanges();
tick(100);
clickOnSave();
const saveButton = fixture.debugElement.query(By.css('[data-automation-id="save-metadata"]'));
saveButton.nativeElement.click();
tick(100);
expect(component.node).toEqual(expectedNode);
@@ -905,244 +775,6 @@ describe('ContentMetadataComponent', () => {
expect(event.stopPropagation).not.toHaveBeenCalled();
});
});
describe('Tags list', () => {
let tagPaging: TagPaging;
beforeEach(() => {
tagPaging = mockTagPaging();
component.displayTags = true;
});
it('should render tags after loading tags in ngOnInit', () => {
spyOn(tagService, 'getTagsByNodeId').and.returnValue(of(tagPaging));
component.ngOnInit();
fixture.detectChanges();
const tagElements = findTagElements();
expect(tagElements).toHaveSize(2);
expect(tagElements[0].nativeElement.textContent).toBe(tagPaging.list.entries[0].entry.tag);
expect(tagElements[1].nativeElement.textContent).toBe(tagPaging.list.entries[1].entry.tag);
expect(tagService.getTagsByNodeId).toHaveBeenCalledWith(node.id);
});
it('should not render tags after loading tags in ngOnInit if displayTags is false', () => {
component.displayTags = false;
spyOn(tagService, 'getTagsByNodeId').and.returnValue(of(tagPaging));
component.ngOnInit();
fixture.detectChanges();
const tagElements = findTagElements();
expect(tagElements).toHaveSize(0);
expect(tagService.getTagsByNodeId).not.toHaveBeenCalled();
});
it('should render tags after loading tags in ngOnChanges', () => {
spyOn(tagService, 'getTagsByNodeId').and.returnValue(of(tagPaging));
component.ngOnChanges({
node: new SimpleChange(undefined, node, false)
});
fixture.detectChanges();
const tagElements = findTagElements();
expect(tagElements).toHaveSize(2);
expect(tagElements[0].nativeElement.textContent).toBe(tagPaging.list.entries[0].entry.tag);
expect(tagElements[1].nativeElement.textContent).toBe(tagPaging.list.entries[1].entry.tag);
expect(tagService.getTagsByNodeId).toHaveBeenCalledWith(node.id);
});
it('should not render tags after loading tags in ngOnChanges if displayTags is false', () => {
component.displayTags = false;
spyOn(tagService, 'getTagsByNodeId').and.returnValue(of(tagPaging));
component.ngOnChanges({
node: new SimpleChange(undefined, node, false)
});
fixture.detectChanges();
const tagElements = findTagElements();
expect(tagElements).toHaveSize(0);
expect(tagService.getTagsByNodeId).not.toHaveBeenCalled();
});
it('should not render tags after loading tags in ngOnChanges if node is not changed', () => {
spyOn(tagService, 'getTagsByNodeId').and.returnValue(of(tagPaging));
component.ngOnChanges({});
fixture.detectChanges();
const tagElements = findTagElements();
expect(tagElements).toHaveSize(0);
expect(tagService.getTagsByNodeId).not.toHaveBeenCalled();
});
it('should not render tags after loading tags in ngOnChanges if node is changed first time', () => {
spyOn(tagService, 'getTagsByNodeId').and.returnValue(of(tagPaging));
component.ngOnChanges({
node: new SimpleChange(undefined, node, true)
});
fixture.detectChanges();
const tagElements = findTagElements();
expect(tagElements).toHaveSize(0);
expect(tagService.getTagsByNodeId).not.toHaveBeenCalled();
});
it('should render tags after loading tags after clicking on Cancel button', fakeAsync(() => {
component.editable = true;
fixture.detectChanges();
TestBed.inject(CardViewContentUpdateService).itemUpdated$.next({
changed: {}
} as UpdateNotification);
tick(500);
fixture.detectChanges();
spyOn(tagService, 'getTagsByNodeId').and.returnValue(of(tagPaging));
clickOnCancel();
component.editable = false;
fixture.detectChanges();
const tagElements = findTagElements();
expect(tagElements).toHaveSize(2);
expect(tagElements[0].nativeElement.textContent).toBe(tagPaging.list.entries[0].entry.tag);
expect(tagElements[1].nativeElement.textContent).toBe(tagPaging.list.entries[1].entry.tag);
expect(tagService.getTagsByNodeId).toHaveBeenCalledOnceWith(node.id);
}));
it('should be hidden when editable is true', () => {
spyOn(tagService, 'getTagsByNodeId').and.returnValue(of(tagPaging));
component.ngOnInit();
fixture.detectChanges();
component.editable = true;
fixture.detectChanges();
expect(findTagElements()).toHaveSize(0);
});
});
describe('Tags creator', () => {
let tagsCreator: TagsCreatorComponent;
beforeEach(() => {
component.editable = true;
component.displayTags = true;
fixture.detectChanges();
tagsCreator = findTagsCreator();
});
it('should have assigned false to tagNameControlVisible initially', () => {
expect(tagsCreator.tagNameControlVisible).toBeFalse();
});
it('should hide showing tag input button after emitting tagNameControlVisibleChange event with true', () => {
tagsCreator.tagNameControlVisibleChange.emit(true);
fixture.detectChanges();
expect(findShowingTagInputButton().hasAttribute('hidden')).toBeTrue();
});
it('should show showing tag input button after emitting tagNameControlVisibleChange event with false', fakeAsync(() => {
tagsCreator.tagNameControlVisibleChange.emit(true);
fixture.detectChanges();
tick();
tagsCreator.tagNameControlVisibleChange.emit(false);
fixture.detectChanges();
tick(100);
expect(findShowingTagInputButton().hasAttribute('hidden')).toBeFalse();
}));
it('should have assigned correct mode', () => {
expect(tagsCreator.mode).toBe(TagsCreatorMode.CREATE_AND_ASSIGN);
});
it('should enable cancel button after emitting tagsChange event', () => {
tagsCreator.tagsChange.emit(['New tag 1', 'New tag 2', 'New tag 3']);
fixture.detectChanges();
expect(findCancelButton().disabled).toBeFalse();
});
it('should enable save button after emitting tagsChange event', () => {
tagsCreator.tagsChange.emit(['New tag 1', 'New tag 2', 'New tag 3']);
fixture.detectChanges();
expect(findSaveButton().disabled).toBeFalse();
});
it('should have assigned false to disabledTagsRemoving', () => {
expect(tagsCreator.disabledTagsRemoving).toBeFalse();
});
it('should have assigned true to disabledTagsRemoving after clicking on update button', () => {
tagsCreator.tagsChange.emit([]);
fixture.detectChanges();
clickOnSave();
expect(tagsCreator.disabledTagsRemoving).toBeTrue();
});
it('should have assigned false to disabledTagsRemoving if forkJoin fails', fakeAsync( () => {
const property = { key: 'properties.property-key', value: 'original-value' } as CardViewBaseItemModel;
const expectedNode = { ...node, name: 'some-modified-value' };
spyOn(nodesApiService, 'updateNode').and.returnValue(of(expectedNode));
const tagPaging = mockTagPaging();
spyOn(tagService, 'getTagsByNodeId').and.returnValue(of(tagPaging));
component.ngOnInit();
spyOn(tagService, 'removeTag').and.returnValue(throwError({}));
spyOn(tagService, 'assignTagsToNode').and.returnValue(EMPTY);
const tagName1 = tagPaging.list.entries[0].entry.tag;
const tagName2 = 'New tag 3';
updateService.update(property, 'updated-value');
tick(600);
fixture.detectChanges();
tagsCreator.tagsChange.emit([tagName1, tagName2]);
clickOnSave();
expect(tagsCreator.disabledTagsRemoving).toBeFalse();
}));
it('should have assigned false to tagNameControlVisible after clicking on update button', () => {
tagsCreator.tagNameControlVisibleChange.emit(true);
tagsCreator.tagsChange.emit([]);
fixture.detectChanges();
clickOnSave();
expect(tagsCreator.tagNameControlVisible).toBeFalse();
});
describe('Setting tags', () => {
let tagPaging: TagPaging;
beforeEach(() => {
tagPaging = mockTagPaging();
spyOn(tagService, 'getTagsByNodeId').and.returnValue(of(tagPaging));
});
it('should assign correct tags after ngOnInit', () => {
component.ngOnInit();
fixture.detectChanges();
expect(tagsCreator.tags).toEqual([tagPaging.list.entries[0].entry.tag, tagPaging.list.entries[1].entry.tag]);
expect(tagService.getTagsByNodeId).toHaveBeenCalledWith(node.id);
});
it('should assign correct tags after ngOnChanges', () => {
component.ngOnInit();
fixture.detectChanges();
expect(tagsCreator.tags).toEqual([tagPaging.list.entries[0].entry.tag, tagPaging.list.entries[1].entry.tag]);
expect(tagService.getTagsByNodeId).toHaveBeenCalledWith(node.id);
});
});
});
it('should show tags creator if editable is true and displayTags is true', () => {
component.editable = true;
component.displayTags = true;
fixture.detectChanges();
expect(findTagsCreator()).toBeDefined();
});
it('should not show tags creator if editable is true and displayTags is false', () => {
component.editable = true;
component.displayTags = false;
fixture.detectChanges();
expect(findTagsCreator()).toBeUndefined();
});
});
const queryDom = (fixture: ComponentFixture<ContentMetadataComponent>, properties: string = 'properties') =>
@@ -16,8 +16,8 @@
*/
import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewEncapsulation } from '@angular/core';
import { Node, TagBody, TagEntry, TagPaging } from '@alfresco/js-api';
import { Observable, Subject, of, zip, forkJoin } from 'rxjs';
import { Node } from '@alfresco/js-api';
import { Observable, Subject, of, zip } from 'rxjs';
import {
CardViewItem,
LogService,
@@ -31,8 +31,6 @@ import { CardViewGroup, PresetConfig } from '../../interfaces/content-metadata.i
import { takeUntil, debounceTime, catchError, map } from 'rxjs/operators';
import { CardViewContentUpdateService } from '../../../common/services/card-view-content-update.service';
import { NodesApiService } from '../../../common/services/nodes-api.service';
import { TagsCreatorMode } from '../../../tag/tags-creator/tags-creator-mode';
import { TagService } from '../../../tag/services/tag.service';
const DEFAULT_SEPARATOR = ', ';
@@ -52,14 +50,7 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
/** Toggles whether the edit button should be shown */
@Input()
set editable(editable: boolean) {
this._editable = editable;
this._assignedTags = [...this.tags];
}
get editable(): boolean {
return this._editable;
}
editable: boolean = false;
/** Toggles whether to display empty values in the card view */
@Input()
@@ -95,25 +86,13 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
@Input()
useChipsForMultiValueProperty: boolean = true;
/** True if tags should be displayed, false otherwise */
@Input()
displayTags = false;
multiValueSeparator: string;
basicProperties$: Observable<CardViewItem[]>;
groupedProperties$: Observable<CardViewGroup[]>;
changedProperties = {};
hasMetadataChanged = false;
tagNameControlVisible = false;
private _assignedTags: string[] = [];
private assignedTagsEntries: TagEntry[] = [];
private _editable = false;
private _tagsCreatorMode = TagsCreatorMode.CREATE_AND_ASSIGN;
private _tags: string[] = [];
private targetProperty: CardViewBaseItemModel;
private _saving = false;
constructor(
private contentMetadataService: ContentMetadataService,
@@ -121,8 +100,7 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
private nodesApiService: NodesApiService,
private logService: LogService,
private translationService: TranslationService,
private appConfig: AppConfigService,
private tagService: TagService
private appConfig: AppConfigService
) {
this.copyToClipboardAction = this.appConfig.get<boolean>('content-metadata.copy-to-clipboard-action');
this.multiValueSeparator = this.appConfig.get<string>('content-metadata.multi-value-pipe-separator') || DEFAULT_SEPARATOR;
@@ -150,22 +128,6 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
this.loadProperties(this.node);
}
get assignedTags(): string[] {
return this._assignedTags;
}
get tags(): string[] {
return this._tags;
}
get tagsCreatorMode(): TagsCreatorMode {
return this._tagsCreatorMode;
}
get saving(): boolean {
return this._saving;
}
protected handleUpdateError(error: Error) {
this.logService.error(error);
@@ -198,9 +160,6 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
if (node) {
this.basicProperties$ = this.getProperties(node);
this.groupedProperties$ = this.contentMetadataService.getGroupedProperties(node, this.preset);
if (this.displayTags) {
this.loadTagsForNode(node.id);
}
}
}
@@ -227,12 +186,7 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
});
}
/**
* Called after clicking save button. It confirms all changes done for metadata. Before clicking on that button they are not saved.
*/
saveChanges() {
this._saving = true;
this.tagNameControlVisible = false;
if (this.hasContentTypeChanged(this.changedProperties)) {
this.contentMetadataService.openConfirmDialog(this.changedProperties).subscribe(() => {
this.updateNode();
@@ -242,39 +196,22 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
}
}
/**
* Register all tags which should be assigned to node. Please note that they are just in "register" state and are not yet saved
* until button for saving data is clicked. Calling that function causes that save button is enabled.
* @param tags array of tags to register, they are not saved yet until we click save button.
*/
storeTagsToAssign(tags: string[]) {
this._tags = tags;
this.hasMetadataChanged = true;
}
private updateNode() {
forkJoin({
updatedNode: this.nodesApiService.updateNode(this.node.id, this.changedProperties),
...(this.displayTags ? this.saveTags() : {})
}).pipe(
this.nodesApiService.updateNode(this.node.id, this.changedProperties).pipe(
catchError((err) => {
this.cardViewContentUpdateService.updateElement(this.targetProperty);
this.handleUpdateError(err);
return of(null);
}))
.subscribe((result) => {
if (result) {
.subscribe((updatedNode) => {
if (updatedNode) {
if (this.hasContentTypeChanged(this.changedProperties)) {
this.cardViewContentUpdateService.updateNodeAspect(this.node);
}
this.revertChanges();
Object.assign(this.node, result.updatedNode);
Object.assign(this.node, updatedNode);
this.nodesApiService.nodeUpdated.next(this.node);
if (Object.keys(result).length > 1 && this.displayTags) {
this.loadTagsForNode(this.node.id);
}
}
this._saving = false;
});
}
@@ -320,31 +257,4 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
private isEmpty(value: any): boolean {
return value === undefined || value === null || value === '';
}
private loadTagsForNode(id: string) {
this.tagService.getTagsByNodeId(id).subscribe((tagPaging) => {
this.assignedTagsEntries = tagPaging.list.entries;
this._tags = tagPaging.list.entries.map((tagEntry) => tagEntry.entry.tag);
this._assignedTags = [...this._tags];
});
}
private saveTags(): { [key: string]: Observable<TagPaging | TagEntry | void> } {
const observables: { [key: string]: Observable<TagPaging | TagEntry | void> } = {};
if (this.tags) {
this.assignedTagsEntries.forEach((tagEntry) => {
if (!this.tags.some((tag) => tagEntry.entry.tag === tag)) {
observables[`${tagEntry.entry.id}Removing`] = this.tagService.removeTag(this.node.id, tagEntry.entry.id);
}
});
if (this.tags.length) {
observables.tagsAssigning = this.tagService.assignTagsToNode(this.node.id, this.tags.map((tag) => {
const tagBody = new TagBody();
tagBody.tag = tag;
return tagBody;
}));
}
}
return observables;
}
}
@@ -22,15 +22,13 @@ import { MaterialModule } from '../material.module';
import { CoreModule } from '@alfresco/adf-core';
import { ContentMetadataComponent } from './components/content-metadata/content-metadata.component';
import { ContentMetadataCardComponent } from './components/content-metadata-card/content-metadata-card.component';
import { TagModule } from '../tag/tag.module';
@NgModule({
imports: [
CommonModule,
MaterialModule,
FlexLayoutModule,
CoreModule,
TagModule
CoreModule
],
exports: [
ContentMetadataComponent,
@@ -27,7 +27,7 @@ import {
DataTableModule,
ObjectDataTableAdapter,
ShowHeaderMode,
ThumbnailService, AppConfigService
ThumbnailService
} from '@alfresco/adf-core';
import { ContentService } from '../../common/services/content.service';
@@ -62,12 +62,6 @@ import { ShareDataRow } from '../data/share-data-row.model';
import { DocumentLoaderNode } from '../models/document-folder.model';
import { matIconRegistryMock } from '../../testing/mat-icon-registry-mock';
import { domSanitizerMock } from '../../testing/dom-sanitizer-mock';
import { MatDialog } from '@angular/material/dialog';
import { FileAutoDownloadComponent } from './file-auto-download/file-auto-download.component';
const mockDialog = {
open: jasmine.createSpy('open')
};
describe('DocumentList', () => {
@@ -77,7 +71,6 @@ describe('DocumentList', () => {
let customResourcesService: CustomResourcesService;
let thumbnailService: ThumbnailService;
let contentService: ContentService;
let appConfigService: AppConfigService;
let fixture: ComponentFixture<DocumentListComponent>;
let element: HTMLElement;
let eventMock: any;
@@ -91,10 +84,7 @@ describe('DocumentList', () => {
TranslateModule.forRoot(),
ContentTestingModule
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
providers: [
{ provide: MatDialog, useValue: mockDialog }
]
schemas: [CUSTOM_ELEMENTS_SCHEMA]
});
beforeEach(() => {
@@ -112,7 +102,6 @@ describe('DocumentList', () => {
customResourcesService = TestBed.inject(CustomResourcesService);
thumbnailService = TestBed.inject(ThumbnailService);
contentService = TestBed.inject(ContentService);
appConfigService = TestBed.inject(AppConfigService);
spyFolder = spyOn(documentListService, 'getFolder').and.returnValue(of({ list: {} }));
spyFolderNode = spyOn(documentListService, 'getFolderNode').and.returnValue(of(new NodeEntry({ entry: {} })));
@@ -1578,30 +1567,6 @@ describe('DocumentList', () => {
}), undefined);
});
it('should display fileAutoDownload dialog if node size exceeds appConfig.viewer.fileAutoDownloadSizeThresholdInMB', async () => {
appConfigService.config = {
...appConfigService.config,
'viewer': {
'enableFileAutoDownload': true,
'fileAutoDownloadSizeThresholdInMB': 10
}
};
documentList.navigationMode = DocumentListComponent.SINGLE_CLICK_NAVIGATION;
const node = { entry: {
...mockNode1,
content: {
...mockNode1.content,
sizeInBytes: 104857600
}
} };
documentList.onNodeClick(node);
fixture.detectChanges();
await fixture.whenStable();
expect(mockDialog.open).toHaveBeenCalledWith(FileAutoDownloadComponent, { disableClose: true, data: node });
});
describe('Preselect nodes', () => {
beforeEach(() => {
@@ -66,10 +66,6 @@ import { LockService } from '../services/lock.service';
import { DocumentLoaderNode } from '../models/document-folder.model';
import { takeUntil } from 'rxjs/operators';
import { ADF_DOCUMENT_PARENT_COMPONENT } from './document-list.token';
import { MatDialog } from '@angular/material/dialog';
import { FileAutoDownloadComponent } from './file-auto-download/file-auto-download.component';
const BYTES_TO_MB_CONVERSION_VALUE = 1048576;
@Component({
selector: 'adf-document-list',
@@ -371,8 +367,7 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
private alfrescoApiService: AlfrescoApiService,
private nodeService: NodesApiService,
private dataTableService: DataTableService,
private lockService: LockService,
private dialog: MatDialog) {
private lockService: LockService) {
this.nodeService.nodeUpdated.subscribe((node) => {
this.dataTableService.rowUpdate.next({id: node.id, obj: {entry: node}});
@@ -763,16 +758,7 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
onPreviewFile(node: NodeEntry) {
if (node) {
const sizeInMB = node.entry?.content?.sizeInBytes / BYTES_TO_MB_CONVERSION_VALUE;
const fileAutoDownloadFlag: boolean = this.appConfig.get('viewer.enableFileAutoDownload', true);
const sizeThreshold: number = this.appConfig.get('viewer.fileAutoDownloadSizeThresholdInMB', 15);
if (fileAutoDownloadFlag && sizeInMB && sizeInMB > sizeThreshold) {
this.dialog.open(FileAutoDownloadComponent, { disableClose: true, data: node });
} else {
this.preview.emit(new NodeEntityEvent(node));
}
this.preview.emit(new NodeEntityEvent(node));
}
}
@@ -1,24 +0,0 @@
<div mat-dialog-title>
<h3>{{ 'ADF-DOCUMENT-LIST.FILE_AUTO_DOWNLOAD_DIALOG.HEADER' | translate }}</h3>
</div>
<mat-dialog-content>
{{ 'ADF-DOCUMENT-LIST.FILE_AUTO_DOWNLOAD_DIALOG.LABEL' | translate }}
</mat-dialog-content>
<mat-dialog-actions align="end">
<button
mat-button
mat-dialog-close
id="cancelButton"
[attr.aria-label]="'ADF-DOCUMENT-LIST.FILE_AUTO_DOWNLOAD_DIALOG.ACTIONS.CANCEL' | translate">
{{ 'ADF-DOCUMENT-LIST.FILE_AUTO_DOWNLOAD_DIALOG.ACTIONS.CANCEL' | translate }}
</button>
<button
mat-button
mat-dialog-close
id="downloadButton"
color="primary"
[attr.aria-label]="'ADF-DOCUMENT-LIST.FILE_AUTO_DOWNLOAD_DIALOG.ACTIONS.DOWNLOAD' | translate"
[adfNodeDownload]="node">
{{ 'ADF-DOCUMENT-LIST.FILE_AUTO_DOWNLOAD_DIALOG.ACTIONS.DOWNLOAD' | translate }}
</button>
</mat-dialog-actions>
@@ -1,76 +0,0 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FileAutoDownloadComponent } from './file-auto-download.component';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { By } from '@angular/platform-browser';
import { CoreTestingModule } from '@alfresco/adf-core';
import { TranslateModule } from '@ngx-translate/core';
import { NO_ERRORS_SCHEMA } from '@angular/core';
const mockDialog = {
close: jasmine.createSpy('close')
};
describe('FileAutoDownloadComponent', () => {
let matDialogRef: MatDialogRef<FileAutoDownloadComponent>;
let fixture: ComponentFixture<FileAutoDownloadComponent>;
const getButton = (buttonId: string) => {
return fixture.debugElement.query(By.css(buttonId)).nativeElement;
};
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [FileAutoDownloadComponent],
imports: [
TranslateModule.forRoot(),
CoreTestingModule
],
schemas: [NO_ERRORS_SCHEMA],
providers: [
{ provide: MatDialogRef, useValue: mockDialog },
{ provide: MAT_DIALOG_DATA, useValue: null }
]
});
fixture = TestBed.createComponent(FileAutoDownloadComponent);
matDialogRef = TestBed.inject(MatDialogRef);
fixture.detectChanges();
});
it('should emit FileAutoDownloadActionsEnum.CANCEL and close dialog when clicking on the cancel button', async () => {
const waitButton = getButton('#cancelButton');
waitButton.dispatchEvent(new Event('click'));
await fixture.detectChanges();
await fixture.whenStable();
expect(matDialogRef.close).toHaveBeenCalled();
});
it('should emit FileAutoDownloadActionsEnum.DOWNLOAD and close dialog when clicking on the wait button', async () => {
const waitButton = getButton('#downloadButton');
waitButton.dispatchEvent(new Event('click'));
await fixture.detectChanges();
await fixture.whenStable();
expect(matDialogRef.close).toHaveBeenCalled();
});
});
@@ -1,28 +0,0 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
import { NodeEntry } from '@alfresco/js-api';
@Component({
selector: 'adf-file-auto-download',
templateUrl: './file-auto-download.component.html'
})
export class FileAutoDownloadComponent {
constructor(@Inject(MAT_DIALOG_DATA) public node: NodeEntry) {}
}
@@ -35,8 +35,6 @@ import { LibraryRoleColumnComponent } from './components/library-role-column/lib
import { LibraryNameColumnComponent } from './components/library-name-column/library-name-column.component';
import { NameColumnComponent } from './components/name-column/name-column.component';
import { FilterHeaderComponent } from './components/filter-header/filter-header.component';
import { FileAutoDownloadComponent } from './components/file-auto-download/file-auto-download.component';
import { ContentDirectiveModule } from '../directives/content-directive.module';
@NgModule({
imports: [
@@ -47,8 +45,7 @@ import { ContentDirectiveModule } from '../directives/content-directive.module';
MaterialModule,
UploadModule,
EditJsonDialogModule,
SearchModule,
ContentDirectiveModule
SearchModule
],
declarations: [
DocumentListComponent,
@@ -59,8 +56,7 @@ import { ContentDirectiveModule } from '../directives/content-directive.module';
NameColumnComponent,
ContentActionComponent,
ContentActionListComponent,
FilterHeaderComponent,
FileAutoDownloadComponent
FilterHeaderComponent
],
exports: [
DocumentListComponent,
@@ -25,7 +25,6 @@ export * from './components/library-status-column/library-status-column.componen
export * from './components/name-column/name-column.component';
export * from './components/filter-header/filter-header.component';
export * from './components/trashcan-name-column/trashcan-name-column.component';
export * from './components/file-auto-download/file-auto-download.component';
// data
export * from './data/share-datatable-adapter';
+2 -57
View File
@@ -73,15 +73,7 @@
"REMOVE": "Remove",
"DOWNLOAD": "Download"
},
"LOADER_LABEL": "Document list loader",
"FILE_AUTO_DOWNLOAD_DIALOG": {
"HEADER": "Preview loading unresponsive",
"LABEL": "Download to view this document, or cancel to continue without preview.",
"ACTIONS": {
"CANCEL": "Cancel",
"DOWNLOAD": "Download"
}
}
"LOADER_LABEL": "Document list loader"
},
"ALFRESCO_DOCUMENT_LIST": {
"BUTTON": {
@@ -140,51 +132,6 @@
},
"BUTTON": {
"ADD": "Add Tag"
},
"TAGS_CREATOR": {
"EXISTING_TAGS": "Existing tags:",
"EXISTING_TAGS_SELECTION": "Select an existing tag:",
"NO_TAGS_CREATED": "No Tags Created",
"NO_EXISTING_TAGS": "No Existing Tags",
"TITLE": "Create Tags",
"CREATE_TAG": "Create: {{tag}}",
"NAME": "Name",
"ERRORS": {
"EXISTING_TAG": "Tag already exists",
"ALREADY_ADDED_TAG": "Tag is already added",
"EMPTY_TAG": "Tag name can't contain only spaces",
"REQUIRED": "Tag name is required",
"FETCH_TAGS": "Error while fetching the tags",
"CREATE_TAGS": "Error while creating the tags"
},
"TOOLTIPS": {
"DELETE_TAG": "Delete tag",
"HIDE_INPUT": "Hide input"
},
"TAGS_LOADING": "Tags loading",
"CREATE_TAGS_SUCCESS": "Tags created successfully"
}
},
"CATEGORIES_MANAGEMENT": {
"CATEGORIES_TITLE": "Categories",
"NO_CATEGORIES_CREATED": "No Categories created",
"NO_CATEGORIES_ASSIGNED": "No Categories assigned",
"ASSIGN_CATEGORIES": "Assign Categories",
"UNASSIGN_CATEGORY": "Unassign Category",
"DELETE_CATEGORY": "Delete Category",
"EXISTING_CATEGORIES": "Existing Categories",
"GENERIC_CREATE": "Create: {{name}}",
"NAME": "Category name",
"LOADING": "Loading",
"HIDE_INPUT": "Hide input",
"ERRORS": {
"NOT_FOUND": "Categories not found",
"REQUIRED": "Category name is required",
"EMPTY_CATEGORY": "Category name can't contain only spaces",
"ALREADY_EXISTS": "Category already exists",
"ALREADY_ADDED_CATEGORY": "Category is already added",
"CREATE_CATEGORIES": "Error while creating categories",
"EXISTING_CATEGORIES": "Some categories already exist"
}
},
"ADF_FILE_UPLOAD": {
@@ -412,9 +359,7 @@
"CREATED_DATE": "Created Date",
"MODIFIER": "Modifier",
"MODIFIED_DATE": "Modified Date",
"CONTENT_TYPE": "Content Type",
"TAGS": "Tags",
"ADD_TAG_TOOLTIP": "Add tag"
"CONTENT_TYPE": "Content Type"
},
"CONTENT_TYPE": {
"DIALOG" :{
@@ -22,7 +22,3 @@ export * from './tag-node-list.component';
export * from './services/tag.service';
export * from './tag.module';
export * from './tags-creator/tags-creator-mode';
export * from './tags-creator/tags-creator.component';
@@ -345,50 +345,5 @@ describe('TagService', () => {
tick();
}));
});
describe('assignTagsToNode', () => {
let singleResult: TagEntry;
let tags: TagBody[];
const nodeId = 'some node id';
beforeEach(() => {
singleResult = new TagEntry();
singleResult.entry = new Tag();
singleResult.entry.tag = 'some name';
const tag = new TagBody();
tag.tag = 'some name';
tags = [tag];
});
it('should call assignTagsToNode on TagsApi with correct parameters', () => {
spyOn(service.tagsApi, 'assignTagsToNode').and.returnValue(Promise.resolve(singleResult));
service.assignTagsToNode(nodeId, tags);
expect(service.tagsApi.assignTagsToNode).toHaveBeenCalledWith(nodeId, tags);
});
it('should return observable which emits paging object for tags', fakeAsync(() => {
const pagingResult = mockTagPaging();
const tag2 = new TagBody();
tag2.tag = 'some other tag';
tags.push(tag2);
spyOn(service.tagsApi, 'assignTagsToNode').and.returnValue(Promise.resolve(pagingResult));
service.assignTagsToNode(nodeId, tags).subscribe((tagsResult) => {
expect(tagsResult).toEqual(pagingResult);
});
tick();
}));
it('should return observable which emits single tag', fakeAsync(() => {
spyOn(service.tagsApi, 'assignTagsToNode').and.returnValue(Promise.resolve(singleResult));
service.assignTagsToNode(nodeId, tags).subscribe((tagsResult) => {
expect(tagsResult).toEqual(singleResult);
});
tick();
}));
});
});
});
@@ -96,7 +96,7 @@ export class TagService {
* @param tag Name of the tag to remove
* @returns Null object when the operation completes
*/
removeTag(nodeId: string, tag: string): Observable<void> {
removeTag(nodeId: string, tag: string): Observable<any> {
const observableRemove = from(this.tagsApi.deleteTagFromNode(nodeId, tag));
observableRemove.subscribe((data) => {
@@ -191,20 +191,6 @@ export class TagService {
);
}
/**
* Assign tags to node. If tag is new then tag is also created additionally, if tag already exists then it is just assigned.
*
* @param nodeId Id of node to which tags should be assigned.
* @param tags List of tags to create and assign or just assign if they already exist.
*
* @return Just linked tags to node or single tag if linked only one tag.
*/
assignTagsToNode(nodeId: string, tags: TagBody[]): Observable<TagPaging | TagEntry> {
return from(this.tagsApi.assignTagsToNode(nodeId, tags)).pipe(
tap((data) => this.refresh.emit(data))
);
}
private handleError(error: any) {
this.logService.error(error);
return throwError(error || 'Server error');
@@ -90,7 +90,7 @@ describe('TagActionsComponent', () => {
it('Tag list click on delete button should delete the tag', async () => {
component.nodeId = 'fake-node-id';
spyOn(tagService, 'removeTag').and.returnValue(of(undefined));
spyOn(tagService, 'removeTag').and.returnValue(of(true));
component.ngOnChanges();
fixture.detectChanges();
@@ -10,8 +10,7 @@
</mat-chip>
</mat-chip-list>
<button mat-button
[hidden]="!limitTagsDisplayed"
[style.left.px]="viewMoreButtonLeftOffset"
*ngIf="limitTagsDisplayed"
class="adf-view-more-button"
[class.adf-hidden-btn]="!calculationsDone"
(click)="displayAllTags($event)"
@@ -4,22 +4,13 @@
flex-direction: row;
width: inherit;
&.adf-flex-column {
flex-direction: column;
}
.adf-view-more-button {
margin: 4px;
color: var(--adf-theme-foreground-text-color-054);
position: absolute;
&[hidden] {
visibility: hidden;
}
}
&.adf-flex-column {
flex-direction: column;
.adf-view-more-button {
position: relative;
}
}
.adf-full-width {
@@ -44,19 +44,15 @@ describe('TagNodeList', () => {
},
{
entry: {tag: 'test3', id: 'fb4213c0-729d-466c-9a6c-ee2e937273bf'}
},
{
entry: {tag: 'test4', id: 'as4213c0-729d-466c-9a6c-ee2e937273as'}
}
]
}
};
let component: TagNodeListComponent;
let component: any;
let fixture: ComponentFixture<TagNodeListComponent>;
let element: HTMLElement;
let tagService: TagService;
let resizeCallback: ResizeObserverCallback;
function findViewMoreButton(): HTMLButtonElement {
return element.querySelector('.adf-view-more-button');
@@ -74,7 +70,6 @@ describe('TagNodeList', () => {
});
beforeEach(() => {
const resizeObserverSpy = spyOn(window, 'ResizeObserver').and.callThrough();
fixture = TestBed.createComponent(TagNodeListComponent);
tagService = TestBed.inject(TagService);
@@ -84,7 +79,6 @@ describe('TagNodeList', () => {
component = fixture.componentInstance;
component.nodeId = 'fake-node-id';
fixture.detectChanges();
resizeCallback = resizeObserverSpy.calls.mostRecent().args[0];
});
describe('Rendering tests', () => {
@@ -104,7 +98,7 @@ describe('TagNodeList', () => {
});
it('Tag list click on delete button should delete the tag', async () => {
spyOn(tagService, 'removeTag').and.returnValue(of(undefined));
spyOn(tagService, 'removeTag').and.returnValue(of(true));
component.ngOnChanges();
fixture.detectChanges();
@@ -143,29 +137,24 @@ describe('TagNodeList', () => {
fixture.detectChanges();
await fixture.whenStable();
expect(findViewMoreButton().hidden).toBeTrue();
expect(findTagChips()).toHaveSize(4);
expect(findViewMoreButton()).toBeNull();
expect(findTagChips().length).toBe(3);
});
});
describe('Limit tags display', () => {
let initialEntries: TagEntry[];
async function renderTags(entries?: TagEntry[]): Promise<any> {
dataTag.list.entries = entries || initialEntries;
if (entries) {
dataTag.list.entries = entries;
}
component.tagsEntries = dataTag.list.entries;
fixture.detectChanges();
await fixture.whenStable();
}
beforeAll(() => {
initialEntries = dataTag.list.entries;
});
beforeEach(() => {
component.limitTagsDisplayed = true;
component.ngOnInit();
element.style.maxWidth = '309px';
element.style.maxWidth = '200px';
});
it('should render view more button when limiting is enabled', async () => {
@@ -173,8 +162,8 @@ describe('TagNodeList', () => {
component.ngOnChanges();
fixture.detectChanges();
await fixture.whenStable();
expect(findViewMoreButton().hidden).toBeFalse();
expect(findTagChips()).toHaveSize(component.tagsEntries.length);
expect(findViewMoreButton()).not.toBeNull();
expect(findTagChips().length).toBe(component.tagsEntries.length);
});
it('should not render view more button when limiting is enabled and all tags fits into container', async () => {
@@ -185,8 +174,8 @@ describe('TagNodeList', () => {
fixture.detectChanges();
await fixture.whenStable();
expect(findViewMoreButton().hidden).toBeTrue();
expect(findTagChips()).toHaveSize(4);
expect(findViewMoreButton()).toBeNull();
expect(findTagChips().length).toBe(3);
});
it('should display all tags when view more button is clicked', async () => {
@@ -201,8 +190,8 @@ describe('TagNodeList', () => {
await fixture.whenStable();
viewMoreButton = findViewMoreButton();
expect(viewMoreButton.hidden).toBeTrue();
expect(findTagChips()).toHaveSize(4);
expect(viewMoreButton).toBeNull();
expect(findTagChips().length).toBe(3);
});
it('should not render view more button when tag takes more than one line and there are no more tags', async () => {
@@ -215,8 +204,8 @@ describe('TagNodeList', () => {
component.ngOnChanges();
fixture.detectChanges();
await fixture.whenStable();
expect(findViewMoreButton().hidden).toBeTrue();
expect(findTagChips()).toHaveSize(component.tagsEntries.length);
expect(findViewMoreButton()).toBeNull();
expect(findTagChips().length).toBe(component.tagsEntries.length);
});
it('should render view more button when tag takes more than one line and there are more tags', async () => {
@@ -234,53 +223,8 @@ describe('TagNodeList', () => {
component.ngOnChanges();
fixture.detectChanges();
await fixture.whenStable();
const viewMoreButton = findViewMoreButton();
expect(viewMoreButton.hidden).toBeFalse();
expect(viewMoreButton.style.left).toBe('0px');
expect(findTagChips()).toHaveSize(component.tagsEntries.length);
});
it('should not render view more button when there is enough space after resizing', async () => {
await renderTags();
component.ngOnChanges();
fixture.detectChanges();
await fixture.whenStable();
element.style.maxWidth = '800px';
resizeCallback([], null);
fixture.detectChanges();
const viewMoreButton = findViewMoreButton();
expect(viewMoreButton.hidden).toBeTrue();
expect(findTagChips()).toHaveSize(4);
});
it('should render view more button when there is not enough space after resizing', async () => {
await renderTags();
element.style.maxWidth = '800px';
component.ngOnChanges();
fixture.detectChanges();
await fixture.whenStable();
element.style.maxWidth = '309px';
resizeCallback([], null);
fixture.detectChanges();
expect(findViewMoreButton().hidden).toBeFalse();
expect(findTagChips()).toHaveSize(component.tagsEntries.length);
});
it('should not render view more button again resizing when there is not enough space if user requested to see all tags', async () => {
await renderTags();
component.ngOnChanges();
fixture.detectChanges();
await fixture.whenStable();
const viewMoreButton = findViewMoreButton();
viewMoreButton.click();
fixture.detectChanges();
element.style.maxWidth = '309px';
resizeCallback([], null);
fixture.detectChanges();
expect(viewMoreButton.hidden).toBeTrue();
expect(findTagChips()).toHaveSize(4);
expect(findViewMoreButton()).not.toBeNull();
expect(findTagChips().length).toBe(component.tagsEntries.length);
});
});
});
@@ -15,22 +15,7 @@
* limitations under the License.
*/
import {
Component,
EventEmitter,
Input,
OnChanges,
Output,
ViewEncapsulation,
OnDestroy,
OnInit,
ViewChild,
ElementRef,
ViewChildren,
QueryList,
ChangeDetectorRef,
AfterViewInit
} from '@angular/core';
import { Component, EventEmitter, Input, OnChanges, Output, ViewEncapsulation, OnDestroy, OnInit, ViewChild, ElementRef, ViewChildren, QueryList } from '@angular/core';
import { TagService } from './services/tag.service';
import { TagEntry } from '@alfresco/js-api';
import { Subject } from 'rxjs';
@@ -48,7 +33,7 @@ import { MatChip } from '@angular/material/chips';
styleUrls: ['./tag-node-list.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class TagNodeListComponent implements OnChanges, OnDestroy, OnInit, AfterViewInit {
export class TagNodeListComponent implements OnChanges, OnDestroy, OnInit {
/* eslint no-underscore-dangle: ["error", { "allow": ["_elementRef"] }]*/
/** The identifier of a node. */
@Input()
@@ -72,29 +57,19 @@ export class TagNodeListComponent implements OnChanges, OnDestroy, OnInit, After
calculationsDone = false;
columnFlexDirection = false;
undisplayedTagsCount = 0;
viewMoreButtonLeftOffset: number;
/** Emitted when a tag is selected. */
@Output()
results = new EventEmitter();
private onDestroy$ = new Subject<boolean>();
private initialLimitTagsDisplayed: boolean;
private initialTagsEntries: TagEntry[] = [];
private viewMoreButtonLeftOffsetBeforeFlexDirection: number;
private requestedDisplayingAllTags = false;
private resizeObserver = new ResizeObserver(() => {
this.calculateTagsToDisplay();
this.changeDetectorRef.detectChanges();
});
/**
* Constructor
*
* @param tagService
* @param changeDetectorRef
*/
constructor(private tagService: TagService, private changeDetectorRef: ChangeDetectorRef) {
constructor(private tagService: TagService) {
}
ngOnChanges() {
@@ -102,7 +77,6 @@ export class TagNodeListComponent implements OnChanges, OnDestroy, OnInit, After
}
ngOnInit() {
this.initialLimitTagsDisplayed = this.limitTagsDisplayed;
this.tagService.refresh
.pipe(takeUntil(this.onDestroy$))
.subscribe(() => this.refreshTag());
@@ -116,21 +90,15 @@ export class TagNodeListComponent implements OnChanges, OnDestroy, OnInit, After
});
}
ngAfterViewInit() {
this.resizeObserver.observe(this.containerView.nativeElement);
}
ngOnDestroy() {
this.onDestroy$.next(true);
this.onDestroy$.complete();
this.resizeObserver.unobserve(this.containerView.nativeElement);
}
refreshTag() {
if (this.nodeId) {
this.tagService.getTagsByNodeId(this.nodeId).subscribe((tagPaging) => {
this.tagsEntries = tagPaging.list.entries;
this.initialTagsEntries = tagPaging.list.entries;
this.results.emit(this.tagsEntries);
});
}
@@ -146,39 +114,29 @@ export class TagNodeListComponent implements OnChanges, OnDestroy, OnInit, After
event.preventDefault();
event.stopPropagation();
this.limitTagsDisplayed = false;
this.requestedDisplayingAllTags = true;
this.resizeObserver.unobserve(this.containerView.nativeElement);
this.refreshTag();
}
private calculateTagsToDisplay() {
if (!this.requestedDisplayingAllTags) {
this.tagsEntries = this.initialTagsEntries;
this.changeDetectorRef.detectChanges();
this.undisplayedTagsCount = 0;
let tagsToDisplay = 1;
const containerWidth: number = this.containerView.nativeElement.clientWidth;
const viewMoreBtnWidth: number = this.containerView.nativeElement.children[1].offsetWidth;
const firstTag = this.tagChips.get(0);
const tagChipMargin = firstTag ? this.getTagChipMargin(this.tagChips.get(0)) : 0;
const tagChipsWidth: number = this.tagChips.reduce((width, val, index) => {
width += val._elementRef.nativeElement.offsetWidth + tagChipMargin;
if (containerWidth - viewMoreBtnWidth > width) {
tagsToDisplay = index + 1;
this.viewMoreButtonLeftOffset = width;
this.viewMoreButtonLeftOffsetBeforeFlexDirection = width;
}
return width;
}, 0);
if ((containerWidth - tagChipsWidth) <= 0) {
this.columnFlexDirection = tagsToDisplay === 1 && (containerWidth < (this.tagChips.get(0)._elementRef.nativeElement.offsetWidth + viewMoreBtnWidth));
this.undisplayedTagsCount = this.tagsEntries.length - tagsToDisplay;
this.tagsEntries = this.tagsEntries.slice(0, tagsToDisplay);
let tagsToDisplay = 1;
const containerWidth: number = this.containerView.nativeElement.clientWidth;
const viewMoreBtnWidth: number = this.containerView.nativeElement.children[1].offsetWidth;
const tagChipMargin = this.getTagChipMargin(this.tagChips.get(0));
const tagChipsWidth: number = this.tagChips.reduce((acc, val, index) => {
if (containerWidth - viewMoreBtnWidth > acc + val._elementRef.nativeElement.offsetWidth) {
tagsToDisplay = index + 1;
}
this.limitTagsDisplayed = this.undisplayedTagsCount ? this.initialLimitTagsDisplayed : false;
this.viewMoreButtonLeftOffset = this.columnFlexDirection ? 0 : this.viewMoreButtonLeftOffsetBeforeFlexDirection;
this.calculationsDone = true;
return acc + val._elementRef.nativeElement.offsetWidth + tagChipMargin;
}, 0);
if ((containerWidth - tagChipsWidth) <= 0) {
this.columnFlexDirection = tagsToDisplay === 1 && (containerWidth < (this.tagChips.get(0)._elementRef.nativeElement.offsetWidth + viewMoreBtnWidth));
this.undisplayedTagsCount = this.tagsEntries.length - tagsToDisplay;
this.tagsEntries = this.tagsEntries.slice(0, tagsToDisplay);
}
if (!this.undisplayedTagsCount) {
this.limitTagsDisplayed = false;
}
this.calculationsDone = true;
}
private getTagChipMargin(chip: MatChip): number {
@@ -24,13 +24,10 @@ import { TagActionsComponent } from './tag-actions.component';
import { TagListComponent } from './tag-list.component';
import { TagNodeListComponent } from './tag-node-list.component';
import { CoreModule } from '@alfresco/adf-core';
import { TagsCreatorComponent } from './tags-creator/tags-creator.component';
import { ContentDirectiveModule } from '../directives/content-directive.module';
@NgModule({
imports: [
CommonModule,
ContentDirectiveModule,
MaterialModule,
FormsModule,
ReactiveFormsModule,
@@ -39,14 +36,12 @@ import { ContentDirectiveModule } from '../directives/content-directive.module';
exports: [
TagActionsComponent,
TagListComponent,
TagNodeListComponent,
TagsCreatorComponent
TagNodeListComponent
],
declarations: [
TagActionsComponent,
TagListComponent,
TagNodeListComponent,
TagsCreatorComponent
TagNodeListComponent
]
})
export class TagModule {}
@@ -1,26 +0,0 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Available modes for tags creator.
* Create mode allows only for creating completely new tags.
* Create and Assign mode allows for both - creation of new tags and selection of existing tags.
*/
export enum TagsCreatorMode {
CREATE,
CREATE_AND_ASSIGN
}
@@ -1,87 +0,0 @@
<div class="adf-tags-creation">
<p
class="adf-no-tags-message"
*ngIf="!tags.length && !tagNameControlVisible">
{{ 'TAG.TAGS_CREATOR.NO_TAGS_CREATED' | translate }}
</p>
<div
class="adf-tags-list"
[class.adf-tags-list-with-scrollbar]="tagsListScrollbarVisible"
#tagsList>
<p
*ngFor="let tag of tags"
class="adf-tag adf-label-with-icon-button">
{{ tag }}
<button
data-automation-id="remove-tag-button"
mat-icon-button
(click)="removeTag(tag)"
[attr.title]="'TAG.TAGS_CREATOR.TOOLTIPS.DELETE_TAG' | translate"
[hidden]="disabledTagsRemoving">
<mat-icon>remove</mat-icon>
</button>
</p>
</div>
<div
class="adf-tag-name-field"
*ngIf="(!tagNameControlVisible && tags.length) || tagNameControlVisible"
[hidden]="!tagNameControlVisible">
<mat-form-field *ngIf="tagNameControlVisible">
<mat-icon matPrefix>search</mat-icon>
<mat-label id="adf-tag-name-input-label">
{{ 'TAG.TAGS_CREATOR.NAME' | translate }}
</mat-label>
<input
#tagNameInput
matInput
autocomplete="off"
[formControl]="tagNameControl"
(keyup.enter)="addTag()"
aria-labelledby="adf-tag-name-input-label"
adf-auto-focus
/>
<mat-error [hidden]="!tagNameControl.invalid">{{ tagNameErrorMessageKey | translate }}</mat-error>
</mat-form-field>
<button
data-automation-id="hide-tag-name-input-button"
mat-icon-button
(click)="hideNameInput()"
[attr.title]="'TAG.TAGS_CREATOR.TOOLTIPS.HIDE_INPUT' | translate">
<mat-icon>remove</mat-icon>
</button>
</div>
</div>
<div
class="adf-existing-tags-panel"
*ngIf="existingTagsPanelVisible">
<ng-container *ngIf="!spinnerVisible || existingTags">
<span
class="adf-create-tag-label"
(click)="addTag()"
[hidden]="tagNameControl.invalid || typing">
{{ 'TAG.TAGS_CREATOR.CREATE_TAG' | translate : { tag: tagNameControl.value } }}
</span>
<p class="adf-existing-tags-label">
{{ existingTagsLabelKey | translate }}
</p>
</ng-container>
<div class="adf-tags-list">
<mat-selection-list
*ngIf="!spinnerVisible || existingTags"
(selectionChange)="addExistingTagToTagsToAssign($event)"
[disabled]="isOnlyCreateMode()">
<mat-list-option
*ngFor="let tagRow of existingTags"
class="adf-tag"
[value]="tagRow">
{{ tagRow.entry.tag }}
</mat-list-option>
<p *ngIf="!existingTags?.length">{{ 'TAG.TAGS_CREATOR.NO_EXISTING_TAGS' | translate }}</p>
</mat-selection-list>
<mat-spinner
*ngIf="spinnerVisible"
[diameter]="50"
[attr.aria-label]="'TAG.TAGS_CREATOR.TAGS_LOADING' | translate">
</mat-spinner>
</div>
</div>
@@ -1,144 +0,0 @@
adf-tags-creator {
display: block;
margin-left: -24px;
&.adf-creator-with-existing-tags-panel {
background-color: var(--theme-background-color);
}
.adf-label-with-icon-button {
display: flex;
justify-content: space-between;
align-items: center;
}
.adf-no-tags-message {
margin-left: 9px;
margin-top: 28.5px;
margin-bottom: 0;
height: 30px;
}
.adf-tag-name-field,
.adf-tag-name-field[hidden] {
display: flex;
align-items: center;
min-height: 57px;
mat-form-field {
width: 100%;
box-sizing: border-box;
padding-right: 3px;
font-size: 14px;
}
}
.adf-create-tag-label {
color: var(--theme-primary-color);
cursor: pointer;
margin-top: -1px;
padding-left: 27px;
overflow-wrap: anywhere;
display: inline-block;
padding-right: 12px;
overflow: auto;
max-height: 7vh;
}
.adf-tags-list {
padding-left: 10px;
padding-right: 0;
overflow: auto;
&.adf-tags-list-with-scrollbar {
padding-right: 7px;
margin-right: -22px;
}
}
.adf-tag {
margin-top: 0;
overflow-wrap: anywhere;
& + .adf-tag {
margin-top: -14px;
}
}
.adf-tags-creation {
padding-left: 27px;
padding-right: 22px;
}
.adf-existing-tags-panel {
background-color: var(--theme-card-background-color);
border-top-left-radius: 6px;
border-top-right-radius: 6px;
padding-top: 12px;
position: relative;
width: 100%;
z-index: 100;
.adf-existing-tags-label {
font-size: 10px;
color: var(--theme-secondary-text-color);
padding-left: 12px;
margin-bottom: 2px;
}
.adf-tags-list {
padding-left: 18px;
margin-top: -2px;
padding-right: 0;
display: flex;
flex-direction: column;
min-height: 75px;
.adf-tag {
margin-bottom: 18px;
margin-top: 0;
padding-right: 12px;
font-size: 14px;
height: auto;
width: unset;
& + .adf-tag {
margin-top: -10px;
}
.mat-list-item-content-reverse {
padding: 0 6px;
.mat-pseudo-checkbox {
display: none;
}
}
}
mat-spinner {
margin: auto;
overflow: unset;
min-height: 25px;
}
.mat-list-item-disabled {
background-color: inherit;
color: inherit;
}
}
}
mat-dialog-actions {
margin-right: -24px;
background-color: inherit;
.adf-save-button {
margin-right: 24px;
}
}
[hidden] {
visibility: hidden;
display: unset;
}
}
@@ -1,689 +0,0 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ComponentFixture, discardPeriodicTasks, fakeAsync, flush, TestBed, tick } from '@angular/core/testing';
import { TagsCreatorComponent } from './tags-creator.component';
import { NotificationService } from '@alfresco/adf-core';
import { By } from '@angular/platform-browser';
import { TranslateModule } from '@ngx-translate/core';
import { MatIconModule } from '@angular/material/icon';
import { MatError, MatFormField, MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { ContentDirectiveModule, TagsCreatorMode, TagService } from '@alfresco/adf-content-services';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { EMPTY, of, throwError } from 'rxjs';
import { DebugElement } from '@angular/core';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { MatListModule, MatSelectionList, MatSelectionListChange } from '@angular/material/list';
describe('TagsCreatorComponent', () => {
let fixture: ComponentFixture<TagsCreatorComponent>;
let component: TagsCreatorComponent;
let tagService: TagService;
let notificationService: NotificationService;
const tagNameFieldSelector = '.adf-tag-name-field';
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [TagsCreatorComponent],
imports: [
ContentDirectiveModule,
MatButtonModule,
MatFormFieldModule,
MatIconModule,
MatInputModule,
MatProgressSpinnerModule,
MatListModule,
NoopAnimationsModule,
ReactiveFormsModule,
TranslateModule.forRoot()
],
providers: [
{
provide: TagService,
useValue: {
findTagByName: () => of(null),
searchTags: () =>
of({
list: {
entries: []
}
})
}
},
{
provide: NotificationService,
useValue: {
showError: () => ({})
}
}
]
});
fixture = TestBed.createComponent(TagsCreatorComponent);
component = fixture.componentInstance;
tagService = TestBed.inject(TagService);
notificationService = TestBed.inject(NotificationService);
fixture.detectChanges();
});
function getNameInput(): HTMLInputElement {
return fixture.debugElement.query(By.css(`.adf-tag-name-field input`))?.nativeElement;
}
function getCreateTagLabel(): HTMLSpanElement {
return fixture.debugElement.query(By.css('.adf-create-tag-label'))?.nativeElement;
}
function getRemoveTagButtons(): HTMLButtonElement[] {
const elements = fixture.debugElement.queryAll(By.css(`[data-automation-id="remove-tag-button"]`));
return elements.map(el => el.nativeElement);
}
function clickAtHideNameInputButton() {
fixture.debugElement.query(By.css(`[data-automation-id="hide-tag-name-input-button"]`)).nativeElement.click();
fixture.detectChanges();
}
function getAddedTags(): string[] {
const tagElements = fixture.debugElement.queryAll(By.css(`.adf-tags-creation .adf-tag`));
return tagElements.map(el => el.nativeElement.firstChild.nodeValue.trim());
}
function addTagToAddedList(tagName: string, addUsingEnter?: boolean, typingTimeout = 300): void {
typeTag(tagName, typingTimeout);
if (addUsingEnter) {
getNameInput().dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter' }));
} else {
getCreateTagLabel().click();
}
tick(300);
fixture.detectChanges();
}
function typeTag(tagName: string, timeout = 300): void {
component.tagNameControlVisible = true;
fixture.detectChanges();
const tagNameInput = getNameInput();
tagNameInput.value = tagName;
tagNameInput.dispatchEvent(new InputEvent('input'));
tick(timeout);
fixture.detectChanges();
}
function findSelectionList(): MatSelectionList {
return fixture.debugElement.query(By.directive(MatSelectionList)).componentInstance;
}
describe('Created tags list', () => {
it('should display no tags created message after initialization', () => {
const message = fixture.debugElement.query(By.css('.adf-no-tags-message')).nativeElement.textContent.trim();
expect(message).toBe('TAG.TAGS_CREATOR.NO_TAGS_CREATED');
});
it('should display all tags which have been typed in input and accepted using enter', fakeAsync(() => {
const tag1 = 'Tag 1';
const tag2 = 'Tag 2';
addTagToAddedList(tag1, true);
addTagToAddedList(tag2, true);
const tagElements = getAddedTags();
expect(tagElements.length).toBe(2);
expect(tagElements[0]).toBe(tag1);
expect(tagElements[1]).toBe(tag2);
}));
it('should display all tags which have been typed in input and accepted by clicking at create label', fakeAsync(() => {
const tag1 = 'Tag 1';
const tag2 = 'Tag 2';
addTagToAddedList(tag1);
addTagToAddedList(tag2);
const tagElements = getAddedTags();
expect(tagElements).toEqual([tag1, tag2]);
}));
it('should not add tag if contains only spaces', fakeAsync(() => {
addTagToAddedList(' ', true);
expect(getAddedTags().length).toBe(0);
}));
it('should not add tag if field is empty', fakeAsync(() => {
addTagToAddedList('', true);
expect(getAddedTags().length).toBe(0);
}));
it('should not duplicate already added tag', fakeAsync(() => {
const tag = 'Some tag';
addTagToAddedList(tag, true);
addTagToAddedList(tag, true);
expect(getAddedTags().length).toBe(1);
}));
it('should not duplicate already existing tag', fakeAsync(() => {
const tag = 'Tag';
spyOn(tagService, 'findTagByName').and.returnValue(of({
entry: {
tag,
id: 'tag-1'
}
}));
addTagToAddedList(tag, true);
expect(getAddedTags().length).toBe(0);
}));
it('should not add tag if hit enter during tags loading', fakeAsync(() => {
addTagToAddedList('Tag', true, 0);
expect(getAddedTags().length).toBe(0);
}));
it('should remove specific tag after clicking at remove icon', fakeAsync(() => {
const tag1 = 'Tag 1';
const tag2 = 'Tag 2';
addTagToAddedList(tag1);
addTagToAddedList(tag2);
getRemoveTagButtons()[0].click();
tick();
fixture.detectChanges();
const tagElements = getAddedTags();
expect(tagElements).toEqual([tag2]);
}));
it('should hide button for removing tag if disabledTagsRemoving is true', fakeAsync(() => {
const tag1 = 'Tag 1';
component.disabledTagsRemoving = true;
addTagToAddedList(tag1);
tick();
expect(getRemoveTagButtons()[0].hasAttribute('hidden')).toBeTrue();
}));
it('should show button for removing tag if disabledTagsRemoving is false', fakeAsync(() => {
const tag1 = 'Tag 1';
component.disabledTagsRemoving = false;
addTagToAddedList(tag1);
tick();
expect(getRemoveTagButtons()[0].hasAttribute('hidden')).toBeFalse();
}));
it('should display tags passed by tags input', () => {
component.tags = ['Passed tag 1', 'Passed tag 2'];
fixture.detectChanges();
expect(getAddedTags()).toEqual(component.tags);
});
});
describe('Tag name field', () => {
it('should be visible if tagNameControlVisible is true', () => {
component.tagNameControlVisible = true;
fixture.detectChanges();
const tagNameField = fixture.debugElement.query(By.css(tagNameFieldSelector));
expect(tagNameField).toBeTruthy();
expect(tagNameField.nativeElement.hasAttribute('hidden')).toBeFalsy();
expect(tagNameField.query(By.directive(MatFormField))).toBeTruthy();
});
it('should be hidden after clicking button for hiding input', fakeAsync(() => {
component.tagNameControlVisible = true;
fixture.detectChanges();
tick(100);
clickAtHideNameInputButton();
const tagNameField = fixture.debugElement.query(By.css(tagNameFieldSelector));
expect(tagNameField).toBeFalsy();
}));
it('should input be autofocused', fakeAsync(() => {
component.tagNameControlVisible = true;
fixture.detectChanges();
tick(100);
expect(getNameInput()).toBe(document.activeElement as HTMLInputElement);
}));
it('should input be autofocused after showing input second time', fakeAsync(() => {
component.tagNameControlVisible = true;
fixture.detectChanges();
tick(100);
clickAtHideNameInputButton();
component.tagNameControlVisible = true;
fixture.detectChanges();
tick(100);
expect(getNameInput()).toBe(document.activeElement as HTMLInputElement);
}));
describe('Errors', () => {
function getFirstError(): string {
const error = fixture.debugElement.query(By.directive(MatError));
return error.nativeElement.textContent;
}
it('should show error for only spaces', fakeAsync(() => {
typeTag(' ');
const error = getFirstError();
expect(error).toBe('TAG.TAGS_CREATOR.ERRORS.EMPTY_TAG');
}));
it('should show error for required', fakeAsync(() => {
typeTag('');
const error = getFirstError();
expect(error).toBe('TAG.TAGS_CREATOR.ERRORS.REQUIRED');
}));
it('should show error when duplicated already added tag', fakeAsync(() => {
const tag = 'Some tag';
addTagToAddedList(tag);
typeTag(tag);
const error = getFirstError();
expect(error).toBe('TAG.TAGS_CREATOR.ERRORS.ALREADY_ADDED_TAG');
}));
it('should show error when duplicated already existing tag', fakeAsync(() => {
const tag = 'Some tag';
spyOn(tagService, 'findTagByName').and.returnValue(of({
entry: {
tag,
id: 'tag-1'
}
}));
typeTag(tag);
const error = getFirstError();
expect(error).toBe('TAG.TAGS_CREATOR.ERRORS.EXISTING_TAG');
}));
it('should error for required when not typed anything and blur input', fakeAsync(() => {
component.tagNameControlVisible = true;
fixture.detectChanges();
tick(100);
getNameInput().blur();
fixture.detectChanges();
const error = getFirstError();
expect(error).toBe('TAG.TAGS_CREATOR.ERRORS.REQUIRED');
flush();
}));
});
});
describe('Existing tags panel', () => {
function getPanel(): DebugElement {
return fixture.debugElement.query(By.css(`.adf-existing-tags-panel`));
}
it('should be visible when input is visible and something is typed in input', fakeAsync(() => {
typeTag('some tag');
expect(getPanel()).toBeTruthy();
}));
it('should not be visible initially when input is hidden and nothing was typed', () => {
expect(getPanel()).toBeFalsy();
});
it('should not be visible when input is visible and empty string is typed in input', fakeAsync(() => {
typeTag(' ');
expect(getPanel()).toBeFalsy();
}));
it('should not be visible when input is visible and nothing has been typed', () => {
component.tagNameControlVisible = true;
fixture.detectChanges();
expect(getPanel()).toBeFalsy();
});
it('should not be visible when something has been typed and input has been hidden', fakeAsync(() => {
typeTag('some tag');
clickAtHideNameInputButton();
expect(getPanel()).toBeFalsy();
}));
describe('Label for tag creation', () => {
it('should be visible', fakeAsync(() => {
typeTag('some tag');
expect(getCreateTagLabel()).toBeTruthy();
}));
it('should not be visible if typed only spaces', fakeAsync(() => {
typeTag(' ');
expect(getCreateTagLabel()).toBeFalsy();
}));
it('should not be visible if required error occurs', fakeAsync(() => {
typeTag('');
expect(getCreateTagLabel()).toBeFalsy();
}));
it('should not be visible when trying to duplicate already added tag', fakeAsync(() => {
const tag = 'Some tag';
addTagToAddedList(tag);
typeTag(tag);
expect(getCreateTagLabel().hasAttribute('hidden')).toBeTruthy();
}));
it('should not be visible when trying to duplicate already existing tag', fakeAsync(() => {
const tag = 'Tag';
spyOn(tagService, 'findTagByName').and.returnValue(of({
entry: {
tag,
id: 'tag-1'
}
}));
typeTag(tag);
expect(getCreateTagLabel().hasAttribute('hidden')).toBeTruthy();
}));
it('should not be visible if typed nothing', () => {
component.tagNameControlVisible = true;
fixture.detectChanges();
expect(getCreateTagLabel()).toBeFalsy();
});
it('should not be visible during typing', fakeAsync(() => {
typeTag('some tag', 0);
expect(getCreateTagLabel()).toBeFalsy();
discardPeriodicTasks();
flush();
}));
});
describe('Existing tags', () => {
function getExistingTags(): string[] {
const tagElements = fixture.debugElement.queryAll(By.css(`.adf-existing-tags-panel .adf-tag .mat-list-text`));
return tagElements.map(el => el.nativeElement.firstChild.nodeValue.trim());
}
it('should call findTagByName on tagService using name set in input', fakeAsync(() => {
spyOn(tagService, 'findTagByName').and.returnValue(EMPTY);
const name = 'Tag';
typeTag(name);
expect(tagService.findTagByName).toHaveBeenCalledWith(name);
}));
it('should call searchTags on tagService using name set in input and correct params', fakeAsync(() => {
spyOn(tagService, 'searchTags').and.returnValue(EMPTY);
const name = 'Tag';
typeTag(name);
expect(tagService.searchTags).toHaveBeenCalledWith(name, { orderBy: 'tag', direction: 'asc' },
false, 0, 15 );
}));
it('should display loaded existing tags', fakeAsync(() => {
const tag1 = 'Tag 1';
const tag2 = 'Tag 2';
spyOn(tagService, 'searchTags').and.returnValue(
of({
list: {
entries: [
{ entry: { tag: tag1 } as any },
{ entry: { tag: tag2 } as any }
],
pagination: {}
}
})
);
typeTag('Tag');
const tagElements = getExistingTags();
expect(tagElements).toEqual([tag1, tag2]);
}));
it('should exclude tags passed through tags input from loaded existing tags', fakeAsync(() => {
const tag1 = 'Tag 1';
const tag2 = 'Tag 2';
component.tags = [tag1];
spyOn(tagService, 'searchTags').and.returnValue(
of({
list: {
entries: [
{ entry: { tag: tag1 } as any },
{ entry: { tag: tag2 } as any }
],
pagination: {}
}
})
);
typeTag('Tag');
expect(getExistingTags()).toEqual([tag2]);
}));
it('should not display existing tags if searching fails', fakeAsync(() => {
spyOn(notificationService, 'showError');
spyOn(tagService, 'searchTags').and.returnValue(throwError({}));
typeTag('Tag');
expect(getExistingTags()).toEqual([]);
expect(notificationService.showError).toHaveBeenCalledWith('TAG.TAGS_CREATOR.ERRORS.FETCH_TAGS');
}));
it('should display exact tag', fakeAsync(() => {
const tag = 'Tag';
spyOn(tagService, 'findTagByName').and.returnValue(of({
entry: {
tag,
id: 'tag-1'
}
}));
typeTag(tag);
const tagElements = getExistingTags();
expect(tagElements).toEqual([tag]);
}));
it('should not display exact tag if that tag was passed through tags input', fakeAsync(() => {
const tag = 'Tag';
component.tags = [tag];
spyOn(tagService, 'findTagByName').and.returnValue(of({
entry: {
tag,
id: 'tag-1'
}
}));
typeTag(tag);
expect(getExistingTags()).toEqual([]);
}));
it('should not display exact tag if exact tag loading fails', fakeAsync(() => {
spyOn(notificationService, 'showError');
spyOn(tagService, 'findTagByName').and.returnValue(throwError({}));
typeTag('Tag');
expect(getExistingTags()).toEqual([]);
expect(notificationService.showError).toHaveBeenCalledWith('TAG.TAGS_CREATOR.ERRORS.FETCH_TAGS');
}));
it('should exact tag be above others existing tags when there are some different existing tags than exact tag', fakeAsync(() => {
const tag = 'Tag';
const tag1 = 'Tag 1';
const tag2 = 'Tag 2';
spyOn(tagService, 'findTagByName').and.returnValue(of({
entry: {
tag,
id: 'tag-1'
}
}));
spyOn(tagService, 'searchTags').and.returnValue(
of({
list: {
entries: [
{ entry: { tag: tag1 } as any },
{ entry: { tag: tag2 } as any }
],
pagination: {}
}
})
);
typeTag(tag);
const tagElements = getExistingTags();
expect(tagElements).toEqual([tag, tag1, tag2]);
}));
it('should selection be disabled if mode is Create', fakeAsync(() => {
component.mode = TagsCreatorMode.CREATE;
spyOn(tagService, 'searchTags').and.returnValue(
of({
list: {
entries: [
{ entry: { tag: 'tag' } as any }
],
pagination: {}
}
})
);
typeTag('Tag');
expect(findSelectionList().disabled).toBeTrue();
}));
it('should selection be enabled if mode is Create And Assign', fakeAsync(() => {
component.mode = TagsCreatorMode.CREATE_AND_ASSIGN;
spyOn(tagService, 'searchTags').and.returnValue(
of({
list: {
entries: [
{ entry: { tag: 'tag' } as any }
],
pagination: {}
}
})
);
typeTag('Tag');
expect(findSelectionList().disabled).toBeFalse();
}));
it('should select existing tag when selectionChange event emits', fakeAsync(() => {
const selectedTag = { entry: { tag: 'tag1' } as any };
const leftTag = 'tag2';
component.mode = TagsCreatorMode.CREATE_AND_ASSIGN;
spyOn(tagService, 'searchTags').and.returnValue(
of({
list: {
entries: [
selectedTag,
{ entry: { tag: leftTag } as any }
],
pagination: {}
}
})
);
typeTag('Tag');
findSelectionList().selectionChange.emit({
options: [{
value: selectedTag
}]
} as MatSelectionListChange);
fixture.detectChanges();
expect(getAddedTags()).toEqual([selectedTag.entry.tag]);
expect(getExistingTags()).toEqual([leftTag]);
}));
});
describe('Spinner', () => {
function getSpinner(): DebugElement {
return fixture.debugElement.query(By.css(`.mat-progress-spinner`));
}
it('should be displayed when existing tags are loading', fakeAsync(() => {
typeTag('tag', 0);
const spinner = getSpinner();
expect(spinner).toBeTruthy();
discardPeriodicTasks();
flush();
}));
it('should not be displayed when existing tags stopped loading', fakeAsync(() => {
typeTag('tag');
const spinner = getSpinner();
expect(spinner).toBeFalsy();
}));
it('should have correct diameter', fakeAsync(() => {
typeTag('tag', 0);
const spinner = getSpinner();
expect(spinner.componentInstance.diameter).toBe(50);
discardPeriodicTasks();
flush();
}));
});
});
});
@@ -1,427 +0,0 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { TagEntry, TagPaging } from '@alfresco/js-api';
import {
Component,
ElementRef,
EventEmitter,
HostBinding,
Input,
OnDestroy,
OnInit,
Output,
ViewChild,
ViewEncapsulation
} from '@angular/core';
import { FormControl, Validators } from '@angular/forms';
import { debounce, distinctUntilChanged, finalize, first, map, takeUntil, tap } from 'rxjs/operators';
import { EMPTY, forkJoin, Observable, Subject, timer } from 'rxjs';
import { NotificationService } from '@alfresco/adf-core';
import { TagsCreatorMode } from './tags-creator-mode';
import { MatSelectionListChange } from '@angular/material/list';
import { TagService } from '../services/tag.service';
interface TagNameControlErrors {
duplicatedExistingTag?: boolean;
duplicatedAddedTag?: boolean;
emptyTag?: boolean;
required?: boolean;
}
const DEFAULT_TAGS_SORTING = {
orderBy: 'tag',
direction: 'asc'
};
/**
* Allows to create multiple tags. That component contains input and two lists. Top list is all created tags, bottom list is searched tags based on input's value.
*/
@Component({
selector: 'adf-tags-creator',
templateUrl: './tags-creator.component.html',
styleUrls: ['./tags-creator.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class TagsCreatorComponent implements OnInit, OnDestroy {
/**
* Mode for component.
* In Create mode we can't select existing tags, we can only create them.
* In Create and Assign mode we can both - create tags and select existing tags.
*/
@Input()
mode: TagsCreatorMode;
/**
* False if tags can be removed from top list, true otherwise.
*/
@Input()
disabledTagsRemoving = false;
/**
* Default top list.
* @param tags tags which should be displayed as default tags for top list.
*/
@Input()
set tags(tags: string[]) {
this._tags = [...tags];
this._spinnerVisible = true;
this._initialExistingTags = null;
this._existingTags = null;
this.loadTags(this.tagNameControl.value);
this.tagNameControl.updateValueAndValidity();
}
get tags(): string[] {
return this._tags;
}
/**
* Decides if input for tags creation/searching should be visible. When input is hidden then panel of existing tags is hidden as well.
* @param tagNameControlVisible true if input should be visible, false otherwise.
*/
@Input()
set tagNameControlVisible(tagNameControlVisible: boolean) {
this._tagNameControlVisible = tagNameControlVisible;
if (tagNameControlVisible) {
this._existingTagsPanelVisible = !!this.tagNameControl.value.trim();
setTimeout(() => {
this.tagNameInputElement.nativeElement.scrollIntoView();
});
} else {
this._existingTagsPanelVisible = false;
}
this.existingTagsPanelVisibilityChange.emit(this.existingTagsPanelVisible);
}
get tagNameControlVisible(): boolean {
return this._tagNameControlVisible;
}
/**
* Emitted when bottom list is showing or hiding.
*/
@Output()
existingTagsPanelVisibilityChange = new EventEmitter<boolean>();
@Output()
/**
* Emitted when tags in top list are changed.
*/
tagsChange = new EventEmitter<string[]>();
/**
* Emitted when input is showing or hiding.
*/
@Output()
tagNameControlVisibleChange = new EventEmitter<boolean>();
readonly nameErrorMessagesByErrors = new Map<keyof TagNameControlErrors, string>([
['duplicatedExistingTag', 'EXISTING_TAG'],
['duplicatedAddedTag', 'ALREADY_ADDED_TAG'],
['emptyTag', 'EMPTY_TAG'],
['required', 'REQUIRED']
]);
private readonly existingTagsListLimit = 15;
private exactTagSet$ = new Subject<void>();
private _tags: string[] = [];
private _tagNameControl = new FormControl<string>(
'',
[
this.validateIfNotAlreadyAdded.bind(this),
Validators.required,
this.validateEmptyTag
],
this.validateIfNotExistingTag.bind(this)
);
private _tagNameControlVisible = false;
private _existingTags: TagEntry[];
private _initialExistingTags: TagEntry[];
private onDestroy$ = new Subject<void>();
private _tagNameErrorMessageKey = '';
private _spinnerVisible = false;
private _typing = false;
private _tagsListScrollbarVisible = false;
private cancelExistingTagsLoading$ = new Subject<void>();
private existingExactTag: TagEntry;
private _existingTagsPanelVisible: boolean;
private _existingTagsLabelKey: string;
@ViewChild('tagsList')
private tagsListElement: ElementRef;
@ViewChild('tagNameInput')
private tagNameInputElement: ElementRef;
constructor(
private tagService: TagService,
private notificationService: NotificationService
) {}
ngOnInit(): void {
this.tagNameControl.valueChanges
.pipe(
map((name: string) => name.trim()),
distinctUntilChanged(),
tap((name: string) => {
this._typing = true;
if (name) {
this._spinnerVisible = true;
this._existingTagsPanelVisible = true;
} else {
this._existingTagsPanelVisible = false;
}
this.existingTagsPanelVisibilityChange.emit(this.existingTagsPanelVisible);
this.cancelExistingTagsLoading$.next();
this._initialExistingTags = null;
this._existingTags = null;
}),
debounce((name: string) => (name ? timer(300) : EMPTY)),
takeUntil(this.onDestroy$)
)
.subscribe((name: string) => this.onTagNameControlValueChange(name));
this.tagNameControl.statusChanges
.pipe(takeUntil(this.onDestroy$))
.subscribe(() => this.setTagNameControlErrorMessageKey());
this.setTagNameControlErrorMessageKey();
}
ngOnDestroy(): void {
this.onDestroy$.next();
this.onDestroy$.complete();
this.cancelExistingTagsLoading$.next();
this.cancelExistingTagsLoading$.complete();
}
@HostBinding('class.adf-creator-with-existing-tags-panel')
get hostClass(): boolean {
return this.existingTagsPanelVisible;
}
get tagNameControl(): FormControl<string> {
return this._tagNameControl;
}
get existingTags(): TagEntry[] {
return this._existingTags;
}
get tagNameErrorMessageKey(): string {
return this._tagNameErrorMessageKey;
}
get spinnerVisible(): boolean {
return this._spinnerVisible;
}
get typing(): boolean {
return this._typing;
}
get tagsListScrollbarVisible(): boolean {
return this._tagsListScrollbarVisible;
}
get existingTagsPanelVisible(): boolean {
return this._existingTagsPanelVisible;
}
get existingTagsLabelKey(): string {
return this._existingTagsLabelKey;
}
/**
* Hide input for typing name for new tag or for searching. When input is hidden then panel of existing tags is hidden as well.
*/
hideNameInput(): void {
this.tagNameControlVisible = false;
this._existingTagsPanelVisible = false;
this.existingTagsPanelVisibilityChange.emit(this.existingTagsPanelVisible);
this.tagNameControlVisibleChange.emit(this.tagNameControlVisible);
}
/**
* Add tags to top list using value which is set in input. Adding tag is not allowed when value in input is invalid
* or if user is still typing what means that validation for input is not called yet.
*/
addTag(): void {
if (!this._typing && !this.tagNameControl.invalid) {
this.tags.push(this.tagNameControl.value.trim());
this.hideNameInput();
this.tagNameControl.setValue('');
this.checkScrollbarVisibility();
this.tagNameControl.markAsUntouched();
this.tagsChange.emit(this.tags);
}
}
/**
* Remove tag from top list. In case that tag was part of search result then that tag is moved to bottom list
* (list of existing tags) after removing so user can reselect it again later.
* @param tag tag's name which should be removed from top list.
*/
removeTag(tag: string): void {
this.removeTagFromArray(this.tags, tag);
this.tagNameControl.updateValueAndValidity();
this.updateExistingTagsListOnRemoveFromTagsToConfirm(tag);
this.checkScrollbarVisibility();
this.tagsChange.emit(this.tags);
}
/**
* Called when user selects any tag from list of existing tags. It moves tag from existing tags list to top list.
* @param change
*/
addExistingTagToTagsToAssign(change: MatSelectionListChange): void {
const selectedTag: TagEntry = change.options[0].value;
this.tags.push(selectedTag.entry.tag);
this.removeTagFromArray(this.existingTags, selectedTag);
this.tagNameControl.updateValueAndValidity();
this.exactTagSet$.next();
this.tagsChange.emit(this.tags);
}
/**
* Checks if component is in Create mode.
* @return true if Create mode, false otherwise.
*/
isOnlyCreateMode(): boolean {
return this.mode === TagsCreatorMode.CREATE;
}
private onTagNameControlValueChange(name: string): void {
this.tagNameControl.markAsTouched();
this.loadTags(name);
}
private loadTags(name: string) {
if (name) {
forkJoin({
exactResult: this.tagService.findTagByName(name),
searchedResult: this.tagService.searchTags(name, DEFAULT_TAGS_SORTING, false, 0, this.existingTagsListLimit)
}).pipe(
takeUntil(this.cancelExistingTagsLoading$),
finalize(() => (this._typing = false))
).subscribe(({ exactResult, searchedResult }: {
exactResult: TagEntry;
searchedResult: TagPaging;
}) => {
if (exactResult) {
this.existingExactTag = exactResult;
this.removeExactTagFromSearchedResult(searchedResult);
searchedResult.list.entries.unshift(exactResult);
} else {
this.existingExactTag = null;
}
this._initialExistingTags = searchedResult.list.entries;
this.excludeAlreadyAddedTags(this._initialExistingTags);
this.exactTagSet$.next();
this._spinnerVisible = false;
}, () => {
this.notificationService.showError('TAG.TAGS_CREATOR.ERRORS.FETCH_TAGS');
this._spinnerVisible = false;
});
} else {
this.existingExactTag = null;
}
}
private removeExactTagFromSearchedResult(searchedResult: TagPaging): void {
const exactTagIndex = searchedResult.list.entries.findIndex(
(row) => this.compareTags(row.entry.tag, this.existingExactTag.entry.tag)
);
if (exactTagIndex > -1) {
searchedResult.list.entries.splice(exactTagIndex, 1);
}
}
private validateIfNotExistingTag(tagNameControl: FormControl<string>): Observable<TagNameControlErrors | null> {
return this.exactTagSet$.pipe(
map<void, TagNameControlErrors | null>(() =>
this.compareTags(tagNameControl.value, this.existingExactTag?.entry?.tag) ? { duplicatedExistingTag: true }
: null
), first()
);
}
private validateIfNotAlreadyAdded(tagNameControl: FormControl<string>): TagNameControlErrors | null {
return this.tags.some((tag) => this.compareTags(tag, tagNameControl.value))
? { duplicatedAddedTag: true }
: null;
}
private compareTags(tagName1?: string, tagName2?: string): boolean {
return tagName1?.trim().toUpperCase() === tagName2?.trim().toUpperCase();
}
private validateEmptyTag(tagNameControl: FormControl<string>): TagNameControlErrors | null {
return tagNameControl.value.length && !tagNameControl.value.trim()
? { emptyTag: true }
: null;
}
private setTagNameControlErrorMessageKey(): void {
this._tagNameErrorMessageKey = this.tagNameControl.invalid
? `TAG.TAGS_CREATOR.ERRORS.${this.nameErrorMessagesByErrors.get(
Object.keys(this.tagNameControl.errors)[0] as keyof TagNameControlErrors
)}`
: '';
}
private checkScrollbarVisibility(): void {
setTimeout(() => {
this._tagsListScrollbarVisible =
this.tagsListElement.nativeElement.scrollHeight >
this.tagsListElement.nativeElement.clientHeight;
});
}
private removeTagFromArray<T>(tags: T[], tag: T) {
tags.splice(tags.indexOf(tag), 1);
}
private updateExistingTagsListOnRemoveFromTagsToConfirm(tag: string) {
const entryForTagAddedToExistingTags = this._initialExistingTags?.find(
(tagEntry) => tagEntry.entry.tag === tag
);
if (entryForTagAddedToExistingTags) {
this.existingTags.unshift(entryForTagAddedToExistingTags);
if (this.existingExactTag) {
if (tag !== this.existingExactTag.entry.tag) {
this.removeTagFromArray(this.existingTags, this.existingExactTag);
this.sortExistingTags();
this.existingTags.unshift(this.existingExactTag);
}
} else {
this.sortExistingTags();
}
this.exactTagSet$.next();
}
}
private sortExistingTags() {
this.existingTags.sort((tagEntry1, tagEntry2) =>
tagEntry1.entry.tag.localeCompare(tagEntry2.entry.tag));
}
private excludeAlreadyAddedTags(tags: TagEntry[]) {
this._existingTags = tags.filter((tag) => !this.tags.includes(tag.entry.tag));
}
}
@@ -91,13 +91,7 @@ export class FormModel implements ProcessFormModel {
processVariables: ProcessVariableModel[] = [];
variables: FormVariableModel[] = [];
constructor(
json?: any,
formValues?: FormValues,
readOnly: boolean = false,
protected formService?: FormValidationService,
enableFixedSpace?: boolean
) {
constructor(json?: any, formValues?: FormValues, readOnly: boolean = false, protected formService?: FormValidationService, enableFixedSpace?: boolean) {
this.readOnly = readOnly;
this.json = json;
@@ -421,10 +415,8 @@ export class FormModel implements ProcessFormModel {
}
setNodeIdValueForViewersLinkedToUploadWidget(linkedUploadWidgetContentSelected: UploadWidgetContentLinkModel) {
const linkedWidgetType = linkedUploadWidgetContentSelected?.options?.linkedWidgetType ?? 'uploadWidget';
const subscribedViewers = this.fieldsCache.filter(field =>
linkedUploadWidgetContentSelected.uploadWidgetId === field.params[linkedWidgetType]
linkedUploadWidgetContentSelected.uploadWidgetId === field.params['uploadWidget']
);
subscribedViewers.forEach(viewer => {
@@ -16,13 +16,11 @@
*/
import { ContentLinkModel } from './content-link.model';
export class UploadWidgetContentLinkModelOptions {
linkedWidgetType: string;
}
export class UploadWidgetContentLinkModel extends ContentLinkModel {
uploadWidgetId: string;
constructor(obj?: any, uploadWidgetId?: string, public options?: UploadWidgetContentLinkModelOptions) {
constructor(obj?: any, uploadWidgetId?: string) {
super(obj);
this.uploadWidgetId = uploadWidgetId;
}
@@ -116,14 +116,10 @@
.adf-login-button {
width: 100%;
height: 36px;
line-height: normal;
line-height: 38px;
box-shadow: none;
}
.adf-login-button .mat-button-wrapper {
display: inline-flex;
}
.adf-login-button-label {
color: var(--theme-accent-color-default-contrast);
}
@@ -148,7 +144,6 @@
.adf-interactive-login-label {
display: flex;
flex-direction: row;
line-height: 38px;
justify-content: center;
}
+1 -5
View File
@@ -96,9 +96,6 @@
--adf-theme-mat-grey-color-a200: mat.get-color-from-palette(mat.$grey-palette, A200),
--adf-theme-mat-grey-color-a400: mat.get-color-from-palette(mat.$grey-palette, A400),
--adf-theme-mat-grey-color-50: mat.get-color-from-palette(mat.$grey-palette, 50),
// spacing
--adf-theme-spacing: map-get($custom-css-variables, 'theme-adf-spacing')
);
// propagates SCSS variables into the CSS variables scope
@@ -117,6 +114,5 @@ $adf-custom-theme-sizes: (
'theme-adf-icon-1-font-size': 17px,
'theme-adf-picture-1-font-size': 18px,
'theme-adf-task-footer-font-size': 18px,
'theme-adf-task-title-font-size': 18px,
'theme-adf-spacing': 16px
'theme-adf-task-title-font-size': 18px
);
@@ -15,7 +15,6 @@
* limitations under the License.
*/
import { fakeEnvironmentList } from '../../common/mock/environment.mock';
import { ApplicationInstanceModel } from '../models/application-instance.model';
export const fakeApplicationInstance: ApplicationInstanceModel[] = [
@@ -23,9 +22,3 @@ export const fakeApplicationInstance: ApplicationInstanceModel[] = [
{ name: 'application-new-2', createdAt: '2018-09-21T12:31:39.000Z', status: 'Pending', theme: 'theme-2', icon: 'favorite_border' },
{ name: 'application-new-3', createdAt: '2018-09-21T12:31:39.000Z', status: 'Pending' }
];
export const fakeApplicationInstanceWithEnvironment: ApplicationInstanceModel[] = [
{ name: 'application-new-1', environmentId: fakeEnvironmentList[0].id, createdAt: '2018-09-21T12:31:39.000Z', status: 'Running', theme: 'theme-2', icon: 'favorite_border' },
{ name: 'application-new-2', environmentId: fakeEnvironmentList[1].id,createdAt: '2018-09-21T12:31:39.000Z', status: 'Pending', theme: 'theme-2', icon: 'favorite_border' },
{ name: 'application-new-3', environmentId: fakeEnvironmentList[2].id,createdAt: '2018-09-21T12:31:39.000Z', status: 'Pending' }
];
@@ -27,7 +27,6 @@ export interface ApplicationInstanceModel {
description?: string;
connectors?: any;
descriptor?: Descriptor;
environmentId?: string;
}
export interface Descriptor {
@@ -20,10 +20,9 @@ import { throwError } from 'rxjs';
import { setupTestBed, AppConfigService, AlfrescoApiService, CoreTestingModule } from '@alfresco/adf-core';
import { HttpErrorResponse } from '@angular/common/http';
import { AppsProcessCloudService } from './apps-process-cloud.service';
import { fakeApplicationInstance, fakeApplicationInstanceWithEnvironment } from '../mock/app-model.mock';
import { fakeApplicationInstance } from '../mock/app-model.mock';
import { ProcessServiceCloudTestingModule } from '../../testing/process-service-cloud.testing.module';
import { TranslateModule } from '@ngx-translate/core';
import { fakeEnvironmentList } from '../../common/mock/environment.mock';
describe('AppsProcessCloudService', () => {
@@ -104,15 +103,4 @@ describe('AppsProcessCloudService', () => {
}
);
});
it('should return label with application name', () => {
const applicationLabel = service.getApplicationLabel(fakeApplicationInstance[0]);
expect(applicationLabel).toBe('application-new-1');
});
it('should return label with application name and environment name', () => {
const applicationLabel = service.getApplicationLabel(fakeApplicationInstanceWithEnvironment[0], fakeEnvironmentList);
expect(applicationLabel).toBe('application-new-1 (test-env-name-1)');
});
});
@@ -21,7 +21,6 @@ import { map, catchError } from 'rxjs/operators';
import { AlfrescoApiService, AppConfigService, LogService } from '@alfresco/adf-core';
import { Oauth2Auth } from '@alfresco/js-api';
import { ApplicationInstanceModel } from '../models/application-instance.model';
import { Environment } from '../../common/interface/environment.interface';
@Injectable({ providedIn: 'root' })
export class AppsProcessCloudService {
@@ -59,16 +58,6 @@ export class AppsProcessCloudService {
this.deployedApps = apps;
}
getApplicationLabel(application: ApplicationInstanceModel, environmentList?: Environment[]): string {
const envName = environmentList?.find((env: Environment) => env.id === application.environmentId)?.name;
if (application.environmentId && environmentList && envName) {
return `${application.name} (${envName})`;
} else {
return application.name;
}
}
private getApplicationsByStatus(status: string, role?: string): Observable<ApplicationInstanceModel[]> {
if (status === '') {
return of([]);
@@ -1,18 +0,0 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './public-api';
@@ -1,21 +0,0 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface Environment {
id: string;
name: string;
}
@@ -1,18 +0,0 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './public-api';
@@ -1,18 +0,0 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './environment.interface';
@@ -1,33 +0,0 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Environment } from '../interface/environment.interface';
export const fakeEnvironmentList: Environment[] = [
{
id: 'test-env-1',
name: 'test-env-name-1'
},
{
id: 'test-env-2',
name: 'test-env-name-2'
},
{
id: 'test-env-3',
name: 'test-env-name-3'
}
];
@@ -1,18 +0,0 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './interface/index';
@@ -962,8 +962,10 @@ describe('FormCloudComponent', () => {
expect(formComponent.isOutcomeButtonEnabled(startProcessOutcome)).toBeTruthy();
});
it('should raise [executeOutcome] event for formService', async () => {
spyOn(formComponent.executeOutcome, 'emit');
it('should raise [executeOutcome] event for formService', (done) => {
formComponent.executeOutcome.subscribe(() => {
done();
});
const outcome = new FormOutcomeModel(new FormModel(), {
id: FormCloudComponent.CUSTOM_OUTCOME_ID,
@@ -972,10 +974,6 @@ describe('FormCloudComponent', () => {
formComponent.form = new FormModel();
formComponent.onOutcomeClicked(outcome);
fixture.detectChanges();
await fixture.whenStable();
expect(formComponent.executeOutcome.emit).toHaveBeenCalledTimes(1);
});
it('should refresh form values when data is changed', (done) => {
@@ -33,8 +33,7 @@ import {
FormValues,
FormModel,
ContentLinkModel,
UploadWidgetContentLinkModel,
FormEvent
UploadWidgetContentLinkModel
} from '@alfresco/adf-core';
import { FormCloudService } from '../services/form-cloud.service';
import { TaskVariableCloud } from '../models/task-variable-cloud.model';
@@ -120,7 +119,6 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
if (content instanceof UploadWidgetContentLinkModel) {
this.form.setNodeIdValueForViewersLinkedToUploadWidget(content);
this.onFormDataRefreshed(this.form);
this.formService.formDataRefreshed.next(new FormEvent(this.form));
} else {
this.formContentClicked.emit(content);
}
@@ -47,29 +47,31 @@ describe('FormDefinitionCloudComponent', () => {
getFormsSpy = spyOn(service, 'getStandAloneTaskForms').and.returnValue(of([{ id: 'fake-form', name: 'fakeForm' } as any]));
});
it('should load the forms by default', async () => {
it('should load the forms by default', () => {
fixture.detectChanges();
await fixture.whenStable();
fixture.detectChanges();
const clickMatSelect = fixture.debugElement.query(By.css(('.mat-select-trigger')));
clickMatSelect.triggerEventHandler('click', null);
fixture.detectChanges();
const options: any = fixture.debugElement.queryAll(By.css('mat-option'));
expect(options[0].nativeElement.innerText.trim()).toBe('ADF_CLOUD_TASK_LIST.START_TASK.FORM.LABEL.NONE');
expect(options[1].nativeElement.innerText.trim()).toBe('fakeForm');
expect(getFormsSpy).toHaveBeenCalled();
fixture.whenStable().then(() => {
fixture.detectChanges();
const clickMatSelect = fixture.debugElement.query(By.css(('.mat-select-trigger')));
clickMatSelect.triggerEventHandler('click', null);
fixture.detectChanges();
const options: any = fixture.debugElement.queryAll(By.css('mat-option'));
expect(options[0].nativeElement.innerText.trim()).toBe('ADF_CLOUD_TASK_LIST.START_TASK.FORM.LABEL.NONE');
expect(options[1].nativeElement.innerText.trim()).toBe('fakeForm');
expect(getFormsSpy).toHaveBeenCalled();
});
});
it('should load only None option when no forms exist', async () => {
it('should load only None option when no forms exist', () => {
getFormsSpy.and.returnValue(of([]));
fixture.detectChanges();
await fixture.whenStable();
fixture.detectChanges();
const clickMatSelect = fixture.debugElement.query(By.css(('.mat-select-trigger')));
clickMatSelect.triggerEventHandler('click', null);
fixture.detectChanges();
const options: any = fixture.debugElement.queryAll(By.css('mat-option'));
expect((options).length).toBe(1);
fixture.whenStable().then(() => {
fixture.detectChanges();
const clickMatSelect = fixture.debugElement.query(By.css(('.mat-select-trigger')));
clickMatSelect.triggerEventHandler('click', null);
fixture.detectChanges();
const options: any = fixture.debugElement.queryAll(By.css('mat-option'));
expect((options).length).toBe(1);
});
});
it('should not preselect any form by default', () => {
@@ -79,17 +81,18 @@ describe('FormDefinitionCloudComponent', () => {
expect(formInput.nodeValue).toBeNull();
});
it('should display the name of the form that is selected', async () => {
it('should display the name of the form that is selected', () => {
fixture.detectChanges();
await fixture.whenStable();
const clickMatSelect = fixture.debugElement.query(By.css(('.mat-select-trigger')));
clickMatSelect.triggerEventHandler('click', null);
fixture.detectChanges();
const options: any = fixture.debugElement.queryAll(By.css('mat-option'));
options[1].triggerEventHandler('click', {});
fixture.detectChanges();
const selected = fixture.debugElement.query(By.css('mat-select'));
const selectedValue = ((selected).nativeElement.innerText);
expect(selectedValue.trim()).toBe('fakeForm');
fixture.whenStable().then(() => {
const clickMatSelect = fixture.debugElement.query(By.css(('.mat-select-trigger')));
clickMatSelect.triggerEventHandler('click', null);
fixture.detectChanges();
const options: any = fixture.debugElement.queryAll(By.css('mat-option'));
options[1].triggerEventHandler('click', {});
fixture.detectChanges();
const selected = fixture.debugElement.query(By.css('mat-select'));
const selectedValue = ((selected).nativeElement.innerText);
expect(selectedValue.trim()).toBe('fakeForm');
});
});
});
@@ -57,8 +57,7 @@ import {
allSourceParamsWithRelativePath,
fakeLocalPhysicalRecordResponse,
displayableCMParams,
fakeLocalPngHavingCMProperties,
mockMyNodeId
fakeLocalPngHavingCMProperties
} from '../../../mocks/attach-file-cloud-widget.mock';
import { ProcessServiceCloudTestingModule } from '../../../../testing/process-service-cloud.testing.module';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
@@ -183,7 +182,6 @@ describe('AttachFileCloudWidgetComponent', () => {
it('should be able to attach files coming from content selector', async () => {
createUploadWidgetField(new FormModel(), 'attach-file-alfresco', [], contentSourceParam);
spyOn(contentCloudNodeSelectorService, 'getNodeIdFromPath').and.returnValue(mockNodeId);
fixture.detectChanges();
await fixture.whenStable();
clickOnAttachFileWidget('attach-file-alfresco');
@@ -220,12 +218,12 @@ describe('AttachFileCloudWidgetComponent', () => {
expect(element.querySelector('#file-1155-icon')).not.toBeNull();
});
it('should be able to set label property for Attach File widget', async () => {
it('should be able to set label property for Attach File widget', () => {
createUploadWidgetField(new FormModel(), 'attach-file', [], onlyLocalParams, false, 'Label', true);
fixture.detectChanges();
await fixture.whenStable();
expect(element.querySelector('label').innerText).toEqual('Label');
fixture.whenStable().then(() => {
expect(element.querySelector('label').innerText).toEqual('Label');
});
});
it('should reset the custom models when the component gets destroyed', () => {
@@ -377,7 +375,6 @@ describe('AttachFileCloudWidgetComponent', () => {
});
it('Should set default user alias (-my-) as rootNodeId if destinationFolderPath contains wrong alias and single upload for Alfresco Content + Locale', async () => {
spyOn(contentCloudNodeSelectorService, 'getNodeIdFromPath').and.returnValue(mockMyNodeId);
createUploadWidgetField(new FormModel(), 'attach-file-alfresco', [], allSourceWithWrongAliasParams, false);
fixture.detectChanges();
await fixture.whenStable();
@@ -390,7 +387,7 @@ describe('AttachFileCloudWidgetComponent', () => {
});
it('Should set default user alias (-my-) as rootNodeId if destinationFolderPath contains wrong alias and multiple upload for Alfresco Content + Locale', async () => {
spyOn(contentCloudNodeSelectorService, 'getNodeIdFromPath').and.returnValue(mockMyNodeId);
createUploadWidgetField(new FormModel(), 'attach-file-alfresco', [], allSourceWithWrongAliasParams, true);
fixture.detectChanges();
await fixture.whenStable();
@@ -403,7 +400,6 @@ describe('AttachFileCloudWidgetComponent', () => {
});
it('Should set default user alias (-my-) as rootNodeId if destinationFolderPath does not have alias for Alfresco Content + Locale', async () => {
spyOn(contentCloudNodeSelectorService, 'getNodeIdFromPath').and.returnValue(mockMyNodeId);
createUploadWidgetField(new FormModel(), 'attach-file-alfresco', [], allSourceWithNoAliasParams, true);
fixture.detectChanges();
await fixture.whenStable();
@@ -466,7 +462,6 @@ describe('AttachFileCloudWidgetComponent', () => {
});
it('Should be able to set default user alias (-my-) as rootNodeId if the nodeId of the alias is not fetched from the api', async () => {
spyOn(contentCloudNodeSelectorService, 'getNodeIdFromPath').and.returnValue(mockMyNodeId);
createUploadWidgetField(new FormModel(), 'attach-file-alfresco', [], contentSourceParam, false);
fixture.detectChanges();
await fixture.whenStable();
@@ -548,11 +543,7 @@ describe('AttachFileCloudWidgetComponent', () => {
await fixture.whenStable();
});
afterEach(() => {
fixture.destroy();
});
it('should remove file when remove is clicked', async () => {
it('should remove file when remove is clicked', (done) => {
fixture.detectChanges();
const menuButton = fixture.debugElement.query(By.css('#file-fake-properties-option-menu')).nativeElement as HTMLButtonElement;
menuButton.click();
@@ -560,8 +551,10 @@ describe('AttachFileCloudWidgetComponent', () => {
const removeOption = fixture.debugElement.query(By.css('#file-fake-properties-remove')).nativeElement as HTMLButtonElement;
removeOption.click();
fixture.detectChanges();
await fixture.whenRenderingDone();
expect(element.querySelector('#file-fake-properties-icon')).toBeNull();
fixture.whenRenderingDone().then(() => {
expect(element.querySelector('#file-fake-properties-icon')).toBeNull();
done();
});
});
it('should download file when download is clicked', (done) => {
@@ -619,31 +612,31 @@ describe('AttachFileCloudWidgetComponent', () => {
expect(updateFormSpy).toHaveBeenCalledWith(expectedValues);
});
it('should display the default menu options if no options are provided', async () => {
it('should display the default menu options if no options are provided', () => {
widget.field.params = onlyLocalParams;
const inputDebugElement = fixture.debugElement.query(
By.css('#attach-file-alfresco')
);
inputDebugElement.triggerEventHandler('change', {
target: { files: [fakeLocalPngAnswer] }
fixture.detectChanges();
fixture.whenStable().then(() => {
const inputDebugElement = fixture.debugElement.query(
By.css('#attach-file-attach')
);
inputDebugElement.triggerEventHandler('change', {
target: { files: [fakeLocalPngAnswer] }
});
fixture.detectChanges();
const menuButton = fixture.debugElement.query(By.css('#file-1155-option-menu')).nativeElement as HTMLButtonElement;
menuButton.click();
fixture.detectChanges();
const showOption = fixture.debugElement.query(By.css('#file-1155-show-file')).nativeElement as HTMLButtonElement;
const downloadOption = fixture.debugElement.query(By.css('#file-1155-download-file')).nativeElement as HTMLButtonElement;
const retrieveMetadataOption = fixture.debugElement.query(By.css('#file-1155-retrieve-file-metadata')).nativeElement as HTMLButtonElement;
const removeOption = fixture.debugElement.query(By.css('#file-1155-remove')).nativeElement as HTMLButtonElement;
expect(showOption).not.toBeNull();
expect(downloadOption).not.toBeNull();
expect(retrieveMetadataOption).toBeNull();
expect(removeOption).not.toBeNull();
});
fixture.detectChanges();
await fixture.whenStable();
const menuButton = fixture.debugElement.query(By.css('#file-fake-properties-option-menu')).nativeElement as HTMLButtonElement;
menuButton.click();
fixture.detectChanges();
await fixture.whenStable();
const showOption = fixture.debugElement.query(By.css('#file-fake-properties-show-file'));
const downloadOption = fixture.debugElement.query(By.css('#file-fake-properties-download-file'));
const retrieveMetadataOption = fixture.debugElement.query(By.css('#file-fake-properties-retrieve-file-metadata'));
const removeOption = fixture.debugElement.query(By.css('#file-fake-properties-remove'));
expect(showOption).not.toBeNull();
expect(downloadOption).not.toBeNull();
expect(retrieveMetadataOption).toBeNull();
expect(removeOption).not.toBeNull();
});
});
@@ -718,7 +711,6 @@ describe('AttachFileCloudWidgetComponent', () => {
});
it('should have been called on attach file when value was empty', async () => {
spyOn(contentCloudNodeSelectorService, 'getNodeIdFromPath').and.returnValue(mockNodeId);
clickOnAttachFileWidget('attach-file-alfresco');
fixture.detectChanges();
await fixture.whenStable();
@@ -729,7 +721,6 @@ describe('AttachFileCloudWidgetComponent', () => {
});
it('should not be called on attach file when has a file previously', async () => {
spyOn(contentCloudNodeSelectorService, 'getNodeIdFromPath').and.returnValue(mockNodeId);
widget.field.value = [fakeMinimalNode];
clickOnAttachFileWidget('attach-file-alfresco');
@@ -19,7 +19,6 @@ import { TranslateModule } from '@ngx-translate/core';
import { FileViewerWidgetComponent } from './file-viewer.widget';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormModel, FormService, FormFieldModel } from '@alfresco/adf-core';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
describe('FileViewerWidgetComponent', () => {
const fakeForm = new FormModel();
@@ -48,8 +47,7 @@ describe('FileViewerWidgetComponent', () => {
TranslateModule.forRoot()
],
declarations: [ FileViewerWidgetComponent ],
providers: [ { provide: FormService, useValue: formServiceStub } ],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
providers: [ { provide: FormService, useValue: formServiceStub } ]
});
formServiceStub = TestBed.inject(FormService);
@@ -292,8 +292,6 @@ export const mockNodeId = new Promise<string>((resolve) => {
resolve('mock-node-id');
});
export const mockMyNodeId = Promise.resolve('-my-');
export const mockNodeIdBasedOnStringVariableValue = new Promise<string>((resolve) => {
resolve('mock-string-value-node-id');
});
@@ -21,7 +21,6 @@ export class TaskQueryCloudRequestModel {
appName: string;
appVersion?: string;
assignee?: string;
environmentId?: string;
claimedDate?: string;
createdDate?: Date;
createdFrom?: string;
@@ -57,7 +56,6 @@ export class TaskQueryCloudRequestModel {
this.appName = obj.appName;
this.appVersion = obj.appVersion;
this.assignee = obj.assignee;
this.environmentId = obj.environmentId;
this.claimedDate = obj.claimedDate;
this.createdDate = obj.createdDate;
this.createdFrom = obj.createdFrom;
@@ -30,7 +30,7 @@ import { ProcessFiltersCloudModule } from '../process-filters-cloud.module';
import { ProcessFilterCloudModel } from '../models/process-filter-cloud.model';
import { ProcessFilterCloudService } from '../services/process-filter-cloud.service';
import { AppsProcessCloudService } from '../../../app/services/apps-process-cloud.service';
import { fakeApplicationInstance, fakeApplicationInstanceWithEnvironment } from './../../../app/mock/app-model.mock';
import { fakeApplicationInstance } from './../../../app/mock/app-model.mock';
import moment from 'moment';
import { PROCESS_FILTERS_SERVICE_TOKEN } from '../../../services/cloud-token.service';
import { LocalPreferenceCloudService } from '../../../services/local-preference-cloud.service';
@@ -41,7 +41,6 @@ import { MatIconTestingModule } from '@angular/material/icon/testing';
import { ProcessDefinitionCloud } from '../../../models/process-definition-cloud.model';
import { mockAppVersions } from '../mock/process-filters-cloud.mock';
import { DATE_FORMAT_CLOUD } from '../../../models/date-format-cloud.model';
import { fakeEnvironmentList } from '../../../common/mock/environment.mock';
describe('EditProcessFilterCloudComponent', () => {
let component: EditProcessFilterCloudComponent;
@@ -444,6 +443,16 @@ describe('EditProcessFilterCloudComponent', () => {
});
});
it('should have floating labels when values are present', async () => {
fixture.detectChanges();
await fixture.whenStable();
const inputLabelsNodes = document.querySelectorAll('mat-form-field');
inputLabelsNodes.forEach(labelNode => {
expect(labelNode.getAttribute('ng-reflect-float-label')).toBe('auto');
});
});
it('should be able to filter filterProperties when input is defined', async () => {
fixture.detectChanges();
await fixture.whenStable();
@@ -1162,13 +1171,4 @@ describe('EditProcessFilterCloudComponent', () => {
expect(component.initiatorOptions).toEqual([{ username: 'user1' }, { username: 'user2' }]);
});
});
it('should add environment name to each application selector option label', () => {
component.environmentList = fakeEnvironmentList;
component.environmentId = fakeEnvironmentList[0].id;
getRunningApplicationsSpy.and.returnValue(of(fakeApplicationInstanceWithEnvironment));
component.getRunningApplications();
expect(component.applicationNames[0].label).toBe('application-new-1 (test-env-name-1)');
});
});
@@ -30,7 +30,6 @@ import { ProcessFilterDialogCloudComponent } from './process-filter-dialog-cloud
import { ProcessCloudService } from '../../services/process-cloud.service';
import { DateCloudFilterType, DateRangeFilter } from '../../../models/date-cloud-filter.model';
import { IdentityUserModel } from '../../../people/models/identity-user.model';
import { Environment } from '../../../common/interface/environment.interface';
export const PROCESS_FILTER_ACTION_SAVE = 'save';
export const PROCESS_FILTER_ACTION_SAVE_AS = 'saveAs';
@@ -77,14 +76,6 @@ export class EditProcessFilterCloudComponent implements OnInit, OnChanges, OnDes
@Input()
actions = DEFAULT_ACTIONS;
/** Environment ID of the application. */
@Input()
environmentId: string;
/** List of environments. */
@Input()
environmentList: Environment[] = [];
/** Toggles editing of process filter actions. */
@Input()
showFilterActions = true;
@@ -127,10 +118,6 @@ export class EditProcessFilterCloudComponent implements OnInit, OnChanges, OnDes
this.id = value.id;
}
if (value?.environmentId) {
this.environmentId = value.environmentId;
}
this.processFilterProperties = this.createAndFilterProperties();
this.processFilterActions = this.createAndFilterActions();
@@ -413,7 +400,7 @@ export class EditProcessFilterCloudComponent implements OnInit, OnChanges, OnDes
.subscribe((applications) => {
if (applications && applications.length > 0) {
applications.map((application) => {
this.applicationNames.push({ label: this.appsProcessCloudService.getApplicationLabel(application, this.environmentList), value: application.name });
this.applicationNames.push({ label: application.name, value: application.name });
});
}
});
@@ -638,11 +625,6 @@ export class EditProcessFilterCloudComponent implements OnInit, OnChanges, OnDes
key: 'id',
value: 'id'
},
{
label: 'EnvironmentId',
key: 'environmentId',
value: 'environmentId'
},
{
label: 'ADF_CLOUD_EDIT_PROCESS_FILTER.LABEL.PROCESS_NAME',
key: 'name',
@@ -1,29 +1,17 @@
<mat-nav-list class="adf-process-filters" *ngIf="filters$ | async as filterList; else loading">
<button
*ngFor="let filter of filterList"
mat-list-item
(click)="onFilterClick(filter)"
[attr.aria-label]="filter.name | translate"
[id]="filter.id"
[attr.data-automation-id]="filter.key + '_filter'"
[class.adf-active]="currentFilter === filter"
>
<div class="adf-process-filters__entry">
<adf-icon
data-automation-id="adf-filter-icon"
*ngIf="showIcons"
[value]="filter.icon">
</adf-icon>
<span
data-automation-id="adf-filter-label"
class="adf-filter-action-button__label">
{{ filter.name | translate }}
</span>
</div>
</button>
</mat-nav-list>
<ng-container *ngIf="filters$ | async as filterList; else loading">
<div *ngFor="let filter of filterList" class="adf-filters__entry" [class.adf-active]="currentFilter === filter">
<button (click)="onFilterClick(filter)"
[attr.aria-label]="filter.name | translate"
[id]="filter.id"
[attr.data-automation-id]="filter.key + '_filter'"
mat-button
class="adf-filter-action-button adf-full-width" fxLayout="row" fxLayoutAlign="space-between center">
<adf-icon data-automation-id="adf-filter-icon" *ngIf="showIcons" [value]="filter.icon"></adf-icon>
<span data-automation-id="adf-filter-label" class="adf-filter-action-button__label">{{ filter.name | translate }}</span>
</button>
</div>
</ng-container>
<ng-template #loading>
<ng-container>
<div class="adf-app-list-spinner">
@@ -1,23 +1,27 @@
.adf-process-filters {
margin-right: calc(-1 * var(--adf-theme-spacing));
.adf {
&-filters__entry {
padding: 12px 0 !important;
height: 24px;
width: 100%;
cursor: pointer;
font-size: var(--theme-body-1-font-size) !important;
font-weight: bold;
opacity: 1;
&__entry {
font-size: var(--theme-body-1-font-size);
color: var(--adf-theme-foreground-text-color-054);
display: flex;
align-items: center;
flex: 1;
height: 100%;
gap: var(--adf-theme-spacing);
.adf-full-width {
display: flex;
width: 100%;
}
.adf-filter-action-button .adf-filter-action-button__label {
padding-left: 20px;
margin: 0 8px !important;
}
&.adf-active,
&:hover {
color: var(--theme-primary-color);
}
}
.adf-active {
.adf-process-filters__entry {
color: var(--theme-primary-color);
opacity: 1;
}
}
}
@@ -102,7 +102,7 @@ describe('ProcessFiltersCloudComponent', () => {
fixture.detectChanges();
await fixture.whenStable();
const filters = fixture.debugElement.queryAll(By.css('.adf-process-filters__entry'));
const filters = fixture.debugElement.queryAll(By.css('.adf-filters__entry'));
expect(component.filters.length).toBe(3);
expect(filters.length).toBe(3);
expect(filters[0].nativeElement.innerText).toContain('FakeAllProcesses');
@@ -46,7 +46,6 @@ export class ProcessFilterCloudModel {
startedDateType: DateCloudFilterType;
suspendedDateType: DateCloudFilterType;
completedDate: Date;
environmentId?: string;
private dateRangeFilterService = new DateRangeFilterService();
private _completedFrom: string;
@@ -61,7 +60,6 @@ export class ProcessFilterCloudModel {
this.id = obj.id || Math.random().toString(36).substring(2, 9);
this.name = obj.name || null;
this.key = obj.key || null;
this.environmentId = obj.environmentId;
this.icon = obj.icon || null;
this.index = obj.index || null;
this.appName = obj.appName || obj.appName === '' ? obj.appName : null;
@@ -61,12 +61,7 @@ export class ProcessFilterCloudService {
value = value || {};
const result = {
appName: appName || value['appName'],
id: id || value['id'],
...(
value['environmentId'] && {
environmentId: value['environmentId']
}
)
id: id || value['id']
};
for (const prop of filterProperties) {
@@ -16,15 +16,13 @@
*/
import { Component, SimpleChange, ViewChild } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import {
AppConfigService,
ColumnsSelectorComponent,
CustomEmptyContentTemplateDirective,
DataColumn,
DataRowEvent,
DataTableModule,
getDataColumnMock,
ObjectDataRow,
setupTestBed
@@ -41,9 +39,6 @@ import { PROCESS_LISTS_PREFERENCES_SERVICE_TOKEN } from '../../../services/cloud
import { LocalPreferenceCloudService } from '../../../services/local-preference-cloud.service';
import { ProcessListCloudPreferences } from '../models/process-cloud-preferences';
import { PROCESS_LIST_CUSTOM_VARIABLE_COLUMN } from '../../../models/data-column-custom-data';
import { HttpClientModule } from '@angular/common/http';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
@Component({
template: `
@@ -64,6 +59,19 @@ class CustomTaskListComponent {
processListCloud: ProcessListCloudComponent;
}
@Component({
template: `
<adf-cloud-process-list>
<adf-custom-empty-content-template>
<p id="custom-id">TEST</p>
</adf-custom-empty-content-template>
</adf-cloud-process-list>
`
})
class EmptyTemplateComponent {
}
describe('ProcessListCloudComponent', () => {
let component: ProcessListCloudComponent;
let fixture: ComponentFixture<ProcessListCloudComponent>;
@@ -550,33 +558,14 @@ describe('ProcessListCloudComponent', () => {
describe('Creating an empty custom template - EmptyTemplateComponent', () => {
@Component({
template: `
<adf-cloud-process-list #processListCloud>
<adf-custom-empty-content-template>
<p id="custom-id">TEST</p>
</adf-custom-empty-content-template>
</adf-cloud-process-list>
`
})
class EmptyTemplateComponent {
@ViewChild(ProcessListCloudComponent)
processListCloud: ProcessListCloudComponent;
}
let fixtureEmpty: ComponentFixture<EmptyTemplateComponent>;
setupTestBed({
imports: [
TranslateModule.forRoot(),
HttpClientModule,
NoopAnimationsModule,
DataTableModule,
MatProgressSpinnerModule
ProcessServiceCloudTestingModule
],
providers: [{ provide: PROCESS_LISTS_PREFERENCES_SERVICE_TOKEN, useExisting: LocalPreferenceCloudService }],
declarations: [EmptyTemplateComponent, ProcessListCloudComponent, CustomEmptyContentTemplateDirective]
declarations: [EmptyTemplateComponent]
});
beforeEach(() => {
@@ -588,16 +577,14 @@ describe('ProcessListCloudComponent', () => {
fixtureEmpty.destroy();
});
it('should render the custom template', async () => {
it('should render the custom template', fakeAsync((done) => {
const emptyList = {list: {entries: []}};
spyOn(processListCloudService, 'getProcessByRequest').and.returnValue(of(emptyList));
fixtureEmpty.componentInstance.processListCloud.isLoading = false;
fixtureEmpty.detectChanges();
await fixtureEmpty.whenStable();
expect(fixtureEmpty.debugElement.query(By.css('#custom-id'))).not.toBeNull();
expect(fixtureEmpty.debugElement.query(By.css('.adf-empty-content'))).toBeNull();
});
component.success.subscribe(() => {
expect(fixtureEmpty.debugElement.query(By.css('#custom-id'))).not.toBeNull();
expect(fixtureEmpty.debugElement.query(By.css('.adf-empty-content'))).toBeNull();
done();
});
}));
});
});
@@ -67,10 +67,6 @@ export class ProcessListCloudComponent extends DataTableSchema<ProcessListDataCo
@Input()
id: string = '';
/** Filter the processes to display only the ones with this environment ID. */
@Input()
environmentId: string = '';
/** Filter the processes to display only the ones with this name. */
@Input()
name: string = '';
@@ -459,7 +455,6 @@ export class ProcessListCloudComponent extends DataTableSchema<ProcessListDataCo
skipCount: this.skipCount,
initiator: this.initiator,
id: this.id,
environmentId: this.environmentId,
name: this.name,
processDefinitionId: this.processDefinitionId,
processDefinitionName: this.processDefinitionName,
@@ -22,7 +22,6 @@ export class ProcessQueryCloudRequestModel {
appVersion?: number | string;
initiator?: null;
id?: string;
environmentId?: string;
name?: string;
processDefinitionId?: string;
processDefinitionName?: string;
@@ -51,7 +50,6 @@ export class ProcessQueryCloudRequestModel {
this.appVersion = obj.appVersion;
this.initiator = obj.initiator;
this.id = obj.id;
this.environmentId = obj.environmentId;
this.name = obj.name;
this.processDefinitionId = obj.processDefinitionId;
this.processDefinitionName = obj.processDefinitionName;
@@ -61,14 +61,13 @@ describe('StartProcessCloudComponent', () => {
const firstChange = new SimpleChange(undefined, 'myApp', true);
const selectOptionByName = async (name: string) => {
const selectOptionByName = (name: string) => {
const selectElement = fixture.nativeElement.querySelector('button#adf-select-process-dropdown');
selectElement.click();
fixture.detectChanges();
await fixture.whenStable();
const options: any = fixture.debugElement.queryAll(By.css('.mat-autocomplete-panel .mat-option'));
const currentOption: DebugElement = options.find((option: DebugElement) => option.nativeElement.querySelector('.mat-option-text').innerHTML.trim() === name);
const options: any = fixture.debugElement.queryAll(By.css('.mat-option-text'));
const currentOption = options.find((option: DebugElement) => option.nativeElement.innerHTML.trim() === name);
if (currentOption) {
currentOption.nativeElement.click();
@@ -105,8 +104,7 @@ describe('StartProcessCloudComponent', () => {
fixture = TestBed.createComponent(StartProcessCloudComponent);
component = fixture.componentInstance;
getDefinitionsSpy = spyOn(processService, 'getProcessDefinitions');
formDefinitionSpy = spyOn(formCloudService, 'getForm');
getDefinitionsSpy = spyOn(processService, 'getProcessDefinitions').and.returnValue(of(fakeProcessDefinitions));
spyOn(processService, 'updateProcess').and.returnValue(of());
startProcessSpy = spyOn(processService, 'startProcess').and.returnValue(of(fakeProcessInstance));
getStartEventFormStaticValuesMappingSpy = spyOn(processService, 'getStartEventFormStaticValuesMapping').and.returnValue(of([]));
@@ -120,9 +118,8 @@ describe('StartProcessCloudComponent', () => {
describe('start a process without start form', () => {
beforeEach(() => {
component.name = 'My formless new process';
component.name = 'My new process';
component.appName = 'myApp';
getDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions));
fixture.detectChanges();
const change = new SimpleChange(null, 'MyApp', true);
component.ngOnChanges({ appName: change });
@@ -140,25 +137,28 @@ describe('StartProcessCloudComponent', () => {
fixture.detectChanges();
tick(550);
fixture.detectChanges();
const startBtn = fixture.nativeElement.querySelector('#button-start');
expect(component.isProcessFormValid()).toBe(true);
expect(startBtn.disabled).toBe(false);
fixture.whenStable().then(() => {
fixture.detectChanges();
const startBtn = fixture.nativeElement.querySelector('#button-start');
expect(component.isProcessFormValid()).toBe(true);
expect(startBtn.disabled).toBe(false);
});
}));
it('should create a process instance if the selection is valid', async () => {
it('should create a process instance if the selection is valid', fakeAsync(() => {
getDefinitionsSpy = getDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions));
component.name = 'My new process';
component.processDefinitionName = 'process';
await selectOptionByName('processwithoutform2');
fixture.detectChanges();
await fixture.whenStable();
selectOptionByName('process');
fixture.whenStable().then(() => {
expect(component.processDefinitionCurrent.name).toBe(JSON.parse(JSON.stringify(fakeProcessDefinitions[1])).name);
const startBtn = fixture.nativeElement.querySelector('#button-start');
expect(component.isProcessFormValid()).toBe(true);
expect(startBtn.disabled).toBe(false);
});
}));
expect(component.processDefinitionCurrent.name).toBe(JSON.parse(JSON.stringify(fakeProcessDefinitions[1])).name);
const startBtn = fixture.nativeElement.querySelector('#button-start');
expect(component.isProcessFormValid()).toBe(true);
expect(startBtn.disabled).toBe(false);
});
it('should have start button disabled when no process is selected', async () => {
component.name = '';
@@ -206,7 +206,10 @@ describe('StartProcessCloudComponent', () => {
component.ngOnChanges({ appName: change });
fixture.detectChanges();
tick(550);
expect(component.resolvedValues).toEqual(staticInputs.concat(values));
fixture.whenStable().then(() => {
expect(component.resolvedValues).toEqual(staticInputs.concat(values));
});
}));
});
@@ -214,11 +217,6 @@ describe('StartProcessCloudComponent', () => {
beforeEach(() => {
component.name = 'My new process with form';
component.appName = 'startformwithoutupload';
getDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions));
fixture.detectChanges();
const change = new SimpleChange(null, 'startformwithoutupload', true);
component.ngOnChanges({ appName: change });
component.values = [{
id: '1',
type: 'string',
@@ -241,113 +239,162 @@ describe('StartProcessCloudComponent', () => {
this['value'] = value;
}
}];
fixture.detectChanges();
});
it('should be able to start a process with a valid form', async () => {
formDefinitionSpy.and.returnValue(of(fakeStartForm));
getDefinitionsSpy.and.returnValue(of(fakeSingleProcessDefinition('processwithform')));
typeValueInto('[data-automation-id="adf-inplace-input"]', 'My new process with form');
typeValueInto('#processDefinitionName', 'processwithform');
it('should be able to start a process with a valid form', fakeAsync(() => {
component.processDefinitionName = 'processwithform';
getDefinitionsSpy.and.returnValue(of(fakeSingleProcessDefinition(component.processDefinitionName)));
fixture.detectChanges();
await fixture.whenStable();
fixture.detectChanges();
const firstNameEl = fixture.nativeElement.querySelector('#firstName');
expect(firstNameEl).toBeDefined();
const lastNameEl = fixture.nativeElement.querySelector('#lastName');
expect(lastNameEl).toBeDefined();
const startBtn = fixture.nativeElement.querySelector('#button-start');
expect(component.formCloud.isValid).toBe(true);
expect(startBtn.disabled).toBe(false);
});
it('should NOT be able to start a process with a form NOT valid', async () => {
formDefinitionSpy.and.returnValue(of(fakeStartFormNotValid));
getDefinitionsSpy.and.returnValue(of(fakeSingleProcessDefinition('processwithform')));
typeValueInto('[data-automation-id="adf-inplace-input"]', 'My new process with form');
typeValueInto('#processDefinitionName', 'processwithform');
fixture.detectChanges();
await fixture.whenStable();
fixture.detectChanges();
const firstNameEl = fixture.nativeElement.querySelector('#firstName');
expect(firstNameEl).toBeDefined();
const lastNameEl = fixture.nativeElement.querySelector('#lastName');
expect(lastNameEl).toBeDefined();
const startBtn = fixture.nativeElement.querySelector('#button-start');
expect(component.formCloud.isValid).toBe(false);
expect(startBtn.disabled).toBe(true);
});
it('should be able to start a process with a prefilled valid form', async () => {
getDefinitionsSpy.and.returnValue(of(fakeSingleProcessDefinition('processwithform')));
formDefinitionSpy.and.returnValue(of(fakeStartForm));
typeValueInto('[data-automation-id="adf-inplace-input"]', 'My new process with form');
typeValueInto('#processDefinitionName', 'processwithform');
fixture.detectChanges();
await fixture.whenStable();
fixture.detectChanges();
await fixture.whenStable();
const firstNameEl = fixture.nativeElement.querySelector('#firstName');
expect(firstNameEl).toBeDefined();
expect(firstNameEl.value).toEqual('FakeName');
const lastNameEl = fixture.nativeElement.querySelector('#lastName');
expect(lastNameEl).toBeDefined();
expect(lastNameEl.value).toEqual('FakeLastName');
const startBtn = fixture.nativeElement.querySelector('#button-start');
expect(component.formCloud.isValid).toBe(true);
expect(startBtn.disabled).toBe(false);
});
it('should NOT be able to start a process with a prefilled NOT valid form', async () => {
getDefinitionsSpy.and.returnValue(of(fakeSingleProcessDefinition('processwithform')));
formDefinitionSpy.and.returnValue(of(fakeStartFormNotValid));
typeValueInto('[data-automation-id="adf-inplace-input"]', 'My new process with form');
typeValueInto('#processDefinitionName', 'processwithform');
fixture.detectChanges();
await fixture.whenStable();
fixture.detectChanges();
await fixture.whenStable();
expect(formDefinitionSpy).toHaveBeenCalled();
const firstNameEl = fixture.nativeElement.querySelector('#firstName');
expect(firstNameEl).toBeDefined();
expect(firstNameEl.value).toEqual('FakeName');
const lastNameEl = fixture.nativeElement.querySelector('#lastName');
expect(lastNameEl).toBeDefined();
expect(lastNameEl.value).toEqual('FakeLastName');
const startBtn = fixture.nativeElement.querySelector('#button-start');
expect(component.formCloud.isValid).toBe(false);
expect(startBtn.disabled).toBe(true);
});
it('should display enabled start process button if the selection is valid', async () => {
getDefinitionsSpy.and.returnValue(of(fakeSingleProcessDefinition('processwithoutform2')));
formDefinitionSpy.and.returnValue(of(fakeStartForm));
typeValueInto('[data-automation-id="adf-inplace-input"]', 'My new process with form');
typeValueInto('#processDefinitionName', 'processwithoutform2');
fixture.detectChanges();
await fixture.whenStable();
const startBtn = fixture.nativeElement.querySelector('#button-start');
expect(startBtn.disabled).toBe(false);
expect(component.isProcessFormValid()).toBe(true);
});
it('should have start button enabled when default values are set', async () => {
getDefinitionsSpy.and.returnValue(of(fakeSingleProcessDefinition('processwithoutform2')));
formDefinitionSpy.and.returnValue(of(fakeStartForm));
formDefinitionSpy = spyOn(formCloudService, 'getForm').and.returnValue(of(fakeStartForm));
const change = new SimpleChange(null, 'MyApp', true);
component.ngOnChanges({ appName: change });
fixture.detectChanges();
await fixture.whenStable();
tick();
typeValueInto('[data-automation-id="adf-inplace-input"]', 'My new process with form');
typeValueInto('#processDefinitionName', 'processwithform');
fixture.detectChanges();
tick(550);
const startBtn = fixture.nativeElement.querySelector('#button-start');
expect(startBtn.disabled).toBe(false);
});
fixture.whenStable().then(() => {
fixture.detectChanges();
const firstNameEl = fixture.nativeElement.querySelector('#firstName');
expect(firstNameEl).toBeDefined();
const lastNameEl = fixture.nativeElement.querySelector('#lastName');
expect(lastNameEl).toBeDefined();
const startBtn = fixture.nativeElement.querySelector('#button-start');
expect(component.formCloud.isValid).toBe(true);
expect(startBtn.disabled).toBe(false);
});
}));
it('should NOT be able to start a process with a form NOT valid', fakeAsync(() => {
getDefinitionsSpy.and.returnValue(of(fakeSingleProcessDefinition(component.processDefinitionName)));
fixture.detectChanges();
formDefinitionSpy = spyOn(formCloudService, 'getForm').and.returnValue(of(fakeStartFormNotValid));
const change = new SimpleChange(null, 'MyApp', true);
component.ngOnChanges({ appName: change });
fixture.detectChanges();
tick();
typeValueInto('[data-automation-id="adf-inplace-input"]', 'My new process with form');
typeValueInto('#processDefinitionName', 'processwithform');
fixture.detectChanges();
tick(550);
fixture.whenStable().then(() => {
fixture.detectChanges();
const firstNameEl = fixture.nativeElement.querySelector('#firstName');
expect(firstNameEl).toBeDefined();
const lastNameEl = fixture.nativeElement.querySelector('#lastName');
expect(lastNameEl).toBeDefined();
const startBtn = fixture.nativeElement.querySelector('#button-start');
expect(component.formCloud.isValid).toBe(false);
expect(startBtn.disabled).toBe(true);
});
}));
it('should be able to start a process with a prefilled valid form', fakeAsync(() => {
component.processDefinitionName = 'processwithform';
getDefinitionsSpy.and.returnValue(of(fakeSingleProcessDefinition(component.processDefinitionName)));
fixture.detectChanges();
formDefinitionSpy = spyOn(formCloudService, 'getForm').and.returnValue(of(fakeStartForm));
const change = new SimpleChange(null, 'MyApp', true);
component.ngOnChanges({ appName: change });
fixture.detectChanges();
tick();
typeValueInto('[data-automation-id="adf-inplace-input"]', 'My new process with form');
typeValueInto('#processDefinitionName', 'processwithform');
fixture.detectChanges();
tick(550);
fixture.whenStable().then(() => {
fixture.detectChanges();
const firstNameEl = fixture.nativeElement.querySelector('#firstName');
expect(firstNameEl).toBeDefined();
expect(firstNameEl.value).toEqual('FakeName');
const lastNameEl = fixture.nativeElement.querySelector('#lastName');
expect(lastNameEl).toBeDefined();
expect(lastNameEl.value).toEqual('FakeLastName');
const startBtn = fixture.nativeElement.querySelector('#button-start');
expect(component.formCloud.isValid).toBe(true);
expect(startBtn.disabled).toBe(false);
});
}));
it('should NOT be able to start a process with a prefilled NOT valid form', fakeAsync(() => {
component.processDefinitionName = 'processwithform';
getDefinitionsSpy.and.returnValue(of(fakeSingleProcessDefinition(component.processDefinitionName)));
fixture.detectChanges();
formDefinitionSpy = spyOn(formCloudService, 'getForm').and.returnValue(of(fakeStartFormNotValid));
const change = new SimpleChange(null, 'MyApp', true);
component.ngOnChanges({ appName: change });
fixture.detectChanges();
tick();
typeValueInto('[data-automation-id="adf-inplace-input"]', 'My new process with form');
typeValueInto('#processDefinitionName', 'processwithform');
fixture.detectChanges();
tick(4500);
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(formDefinitionSpy).toHaveBeenCalled();
const firstNameEl = fixture.nativeElement.querySelector('#firstName');
expect(firstNameEl).toBeDefined();
expect(firstNameEl.value).toEqual('FakeName');
const lastNameEl = fixture.nativeElement.querySelector('#lastName');
expect(lastNameEl).toBeDefined();
expect(lastNameEl.value).toEqual('FakeLastName');
const startBtn = fixture.nativeElement.querySelector('#button-start');
expect(component.formCloud.isValid).toBe(false);
expect(startBtn.disabled).toBe(true);
});
}));
it('should display enabled start process button if the selection is valid', fakeAsync(() => {
component.name = 'testFormWithProcess';
component.processDefinitionName = 'processwithoutform2';
getDefinitionsSpy.and.returnValue(of(fakeSingleProcessDefinition(component.processDefinitionName)));
fixture.detectChanges();
formDefinitionSpy = spyOn(formCloudService, 'getForm').and.returnValue(of(fakeStartForm));
const change = new SimpleChange(null, 'MyApp', true);
component.ngOnChanges({ appName: change });
fixture.detectChanges();
tick(550);
fixture.whenStable().then(() => {
fixture.detectChanges();
const startBtn = fixture.nativeElement.querySelector('#button-start');
expect(startBtn.disabled).toBe(false);
expect(component.formCloud.isValid).toBe(true);
expect(component.isProcessFormValid()).toBe(true);
});
}));
it('should have start button enabled when default values are set', fakeAsync(() => {
component.name = 'testFormWithProcess';
component.processDefinitionName = 'processwithoutform2';
getDefinitionsSpy.and.returnValue(of(fakeSingleProcessDefinition(component.processDefinitionName)));
fixture.detectChanges();
formDefinitionSpy = spyOn(formCloudService, 'getForm').and.returnValue(of(fakeStartForm));
const change = new SimpleChange(null, 'MyApp', true);
component.ngOnChanges({ appName: change });
fixture.detectChanges();
tick(550);
fixture.whenStable().then(() => {
fixture.detectChanges();
const startBtn = fixture.nativeElement.querySelector('#button-start');
expect(startBtn.disabled).toBe(false);
});
}));
});
describe('process definitions list', () => {
@@ -355,60 +402,55 @@ describe('StartProcessCloudComponent', () => {
beforeEach(() => {
component.name = 'My new process';
component.appName = 'myApp';
getDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions));
fixture.detectChanges();
const change = new SimpleChange(null, 'MyApp', true);
component.ngOnChanges({ appName: change });
fixture.detectChanges();
});
it('should call service to fetch process definitions with appId', async () => {
await fixture.whenStable();
expect(getDefinitionsSpy).toHaveBeenCalledWith('MyApp');
it('should call service to fetch process definitions with appId', () => {
fixture.whenStable().then(() => {
expect(getDefinitionsSpy).toHaveBeenCalledWith('myApp');
});
});
it('should display the correct number of processes in the select list', async () => {
await fixture.whenStable();
const arrowButton = fixture.nativeElement.querySelector('button#adf-select-process-dropdown');
arrowButton.click();
fixture.detectChanges();
const processLists = fixture.debugElement.query(By.css('.mat-autocomplete-panel'));
expect(processLists.children.length).toBe(4);
it('should display the correct number of processes in the select list', () => {
fixture.whenStable().then(() => {
const selectElement = fixture.nativeElement.querySelector('mat-select');
expect(selectElement.children.length).toBe(1);
});
});
it('should display the option def details', async () => {
it('should display the option def details', () => {
component.processDefinitionList = fakeProcessDefinitions;
fixture.detectChanges();
await fixture.whenStable();
const selectElement = fixture.nativeElement.querySelector('button#adf-select-process-dropdown');
selectElement.click();
fixture.detectChanges();
const optionElement = fixture.debugElement.queryAll(By.css('.mat-autocomplete-panel .mat-option'));
expect(selectElement).not.toBeNull();
expect(selectElement).toBeDefined();
expect(optionElement).not.toBeNull();
expect(optionElement).toBeDefined();
fixture.whenStable().then(() => {
const selectElement = fixture.nativeElement.querySelector('mat-select > .mat-select-trigger');
const optionElement = fixture.nativeElement.querySelectorAll('mat-option');
selectElement.click();
expect(selectElement).not.toBeNull();
expect(selectElement).toBeDefined();
expect(optionElement).not.toBeNull();
expect(optionElement).toBeDefined();
});
});
it('should display the key when the processDefinition name is empty or null', async () => {
it('should display the key when the processDefinition name is empty or null', () => {
component.processDefinitionList = fakeNoNameProcessDefinitions;
fixture.detectChanges();
await fixture.whenStable();
const selectElement = fixture.nativeElement.querySelector('button#adf-select-process-dropdown');
selectElement.click();
fixture.detectChanges();
const optionElement = fixture.debugElement.queryAll(By.css('.mat-autocomplete-panel .mat-option'));
expect(selectElement).not.toBeNull();
expect(selectElement).toBeDefined();
expect(optionElement).not.toBeNull();
expect(optionElement[0].nativeElement.textContent.trim()).toBe('NewProcess 1');
fixture.whenStable().then(() => {
const selectElement = fixture.nativeElement.querySelector('mat-select > .mat-select-trigger');
const optionElement = fixture.nativeElement.querySelectorAll('mat-option');
selectElement.click();
expect(selectElement).not.toBeNull();
expect(selectElement).toBeDefined();
expect(optionElement).not.toBeNull();
expect(optionElement[0].textContent).toBe('NewProcess 1');
});
});
it('should indicate an error to the user if process defs cannot be loaded', async () => {
getDefinitionsSpy.and.returnValue(throwError({}));
getDefinitionsSpy = getDefinitionsSpy.and.returnValue(throwError({}));
const change = new SimpleChange('myApp', 'myApp1', true);
component.ngOnChanges({ appName: change });
@@ -420,7 +462,7 @@ describe('StartProcessCloudComponent', () => {
});
it('should show no process available message when no process definition is loaded', async () => {
getDefinitionsSpy.and.returnValue(of([]));
getDefinitionsSpy = getDefinitionsSpy.and.returnValue(of([]));
const change = new SimpleChange('myApp', 'myApp1', true);
component.ngOnChanges({ appName: change });
@@ -433,7 +475,7 @@ describe('StartProcessCloudComponent', () => {
});
it('should select automatically the processDefinition if the app contain only one', async () => {
getDefinitionsSpy.and.returnValue(of([fakeProcessDefinitions[0]]));
getDefinitionsSpy = getDefinitionsSpy.and.returnValue(of([fakeProcessDefinitions[0]]));
const change = new SimpleChange('myApp', 'myApp1', true);
component.ngOnChanges({ appName: change });
@@ -443,30 +485,29 @@ describe('StartProcessCloudComponent', () => {
expect(component.processForm.controls['processDefinition'].value).toBe(JSON.parse(JSON.stringify(fakeProcessDefinitions[0])).name);
});
it('should select automatically the form when processDefinition is selected as default', async () => {
getDefinitionsSpy.and.returnValue(of([fakeProcessDefinitions[2]]));
formDefinitionSpy.and.returnValue(of(fakeStartForm));
it('should select automatically the form when processDefinition is selected as default', fakeAsync(() => {
getDefinitionsSpy = getDefinitionsSpy.and.returnValue(of([fakeProcessDefinitions[0]]));
formDefinitionSpy = spyOn(formCloudService, 'getForm').and.returnValue(of(fakeStartForm));
const change = new SimpleChange('myApp', 'myApp1', true);
component.ngOnInit();
component.ngOnChanges({ appName: change });
component.processForm.controls['processDefinition'].setValue('process');
fixture.detectChanges();
await fixture.whenStable();
await selectOptionByName('processwithform');
tick(3000);
component.processDefinitionName = fakeProcessDefinitions[0].name;
component.setProcessDefinitionOnForm(fakeProcessDefinitions[0].name);
fixture.detectChanges();
await fixture.whenStable();
tick(3000);
component.processDefinitionName = fakeProcessDefinitions[2].name;
component.setProcessDefinitionOnForm(fakeProcessDefinitions[2].name);
fixture.detectChanges();
await fixture.whenStable();
const processForm = fixture.nativeElement.querySelector('adf-cloud-form');
expect(component.hasForm()).toBeTruthy();
expect(processForm).not.toBeNull();
});
fixture.whenStable().then(() => {
const processForm = fixture.nativeElement.querySelector('adf-cloud-form');
expect(component.hasForm()).toBeTruthy();
expect(processForm).not.toBeNull();
});
}));
it('should not select automatically any processDefinition if the app contain multiple process and does not have any processDefinition as input', async () => {
getDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions));
getDefinitionsSpy = getDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions));
component.appName = 'myApp';
component.ngOnChanges({});
@@ -476,27 +517,23 @@ describe('StartProcessCloudComponent', () => {
expect(component.processPayloadCloud.name).toBeNull();
});
it('should select the right process when the processKey begins with the name', async () => {
getDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions));
formDefinitionSpy.and.returnValue(of(fakeStartForm));
it('should select the right process when the processKey begins with the name', fakeAsync(() => {
getDefinitionsSpy = getDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions));
component.name = 'My new process';
component.processDefinitionName = 'process';
component.appName = 'myApp';
component.ngOnChanges({});
selectOptionByName('process');
fixture.detectChanges();
await fixture.whenStable();
expect(component.processDefinitionCurrent.name).toBe(JSON.parse(JSON.stringify(fakeProcessDefinitions[3])).name);
expect(component.processDefinitionCurrent.key).toBe(JSON.parse(JSON.stringify(fakeProcessDefinitions[3])).key);
});
fixture.whenStable().then(() => {
expect(component.processDefinitionCurrent.name).toBe(JSON.parse(JSON.stringify(fakeProcessDefinitions[3])).name);
expect(component.processDefinitionCurrent.key).toBe(JSON.parse(JSON.stringify(fakeProcessDefinitions[3])).key);
});
}));
describe('dropdown', () => {
it('should hide the process dropdown button if showSelectProcessDropdown is false', async () => {
fixture.detectChanges();
getDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions));
getDefinitionsSpy = getDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions));
component.appName = 'myApp';
component.showSelectProcessDropdown = false;
component.ngOnChanges({});
@@ -510,7 +547,7 @@ describe('StartProcessCloudComponent', () => {
it('should show the process dropdown button if showSelectProcessDropdown is false', async () => {
fixture.detectChanges();
getDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions));
getDefinitionsSpy = getDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions));
component.appName = 'myApp';
component.processDefinitionName = 'NewProcess 2';
component.showSelectProcessDropdown = true;
@@ -525,7 +562,7 @@ describe('StartProcessCloudComponent', () => {
it('should show the process dropdown button by default', async () => {
fixture.detectChanges();
getDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions));
getDefinitionsSpy = getDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions));
component.appName = 'myApp';
component.processDefinitionName = 'NewProcess 2';
component.ngOnChanges({});
@@ -544,27 +581,25 @@ describe('StartProcessCloudComponent', () => {
const change = new SimpleChange('myApp', 'myApp1', false);
beforeEach(() => {
component.name = 'My new process';
component.appName = 'myApp';
fixture.detectChanges();
});
it('should have floating labels for process name and type', async () => {
getDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions));
component.ngOnChanges({ appName: change });
component.appName = 'myApp';
component.processDefinitionName = 'NewProcess 2';
component.ngOnChanges({});
fixture.detectChanges();
await fixture.whenStable();
component.processForm.controls.processInstanceName.setValue('My sharona');
component.processForm.controls.processDefinition.setValue('process');
fixture.detectChanges();
const inputLabelsNodes = document.querySelectorAll('.mat-form-field-label');
expect(inputLabelsNodes.length).toBe(2);
const inputLabelsNodes = document.querySelectorAll('.adf-start-process .adf-process-input-container');
inputLabelsNodes.forEach(labelNode => {
expect(labelNode.getAttribute('ng-reflect-float-label')).toBe('always');
});
});
it('should reload processes when appName input changed', async () => {
getDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions));
component.ngOnChanges({ appName: firstChange });
component.ngOnChanges({ appName: change });
@@ -575,7 +610,6 @@ describe('StartProcessCloudComponent', () => {
});
it('should reload processes ONLY when appName input changed', async () => {
getDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions));
component.ngOnChanges({ appName: firstChange });
fixture.detectChanges();
@@ -587,30 +621,28 @@ describe('StartProcessCloudComponent', () => {
});
it('should get current processDef', () => {
getDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions));
component.ngOnChanges({ appName: change });
fixture.detectChanges();
expect(getDefinitionsSpy).toHaveBeenCalled();
expect(component.processDefinitionList).toBe(fakeProcessDefinitions);
});
it('should display the matching results in the dropdown as the user types down', async () => {
getDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions));
it('should display the matching results in the dropdown as the user types down', fakeAsync(() => {
component.processDefinitionList = fakeProcessDefinitions;
component.ngOnInit();
component.ngOnChanges({ appName: change });
fixture.detectChanges();
typeValueInto('#processDefinitionName', 'process');
component.processForm.controls['processDefinition'].setValue('process');
fixture.detectChanges();
await fixture.whenStable();
tick(3000);
expect(component.filteredProcesses.length).toEqual(4);
typeValueInto('#processDefinitionName', 'processwithfo');
component.processForm.controls['processDefinition'].setValue('processwithfo');
fixture.detectChanges();
await fixture.whenStable();
tick(3000);
expect(component.filteredProcesses.length).toEqual(1);
});
}));
it('should display the process definion field as empty if are more than one process definition in the list', async () => {
getDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions));
@@ -630,8 +662,7 @@ describe('StartProcessCloudComponent', () => {
fixture.detectChanges();
component.name = 'NewProcess 1';
component.appName = 'myApp';
getDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions));
fixture.detectChanges();
component.ngOnChanges({});
});
it('should see start button', async () => {
@@ -715,22 +746,19 @@ describe('StartProcessCloudComponent', () => {
expect(startProcessSpy).toHaveBeenCalledWith(component.appName, payload);
});
it('should call service with the correct parameters when variables are undefined and formCloud is defined', async () => {
getDefinitionsSpy.and.returnValue(of([fakeProcessDefinitions[2]]));
formDefinitionSpy.and.returnValue(of(fakeStartForm));
it('should call service with the correct parameters when variables are undefined and formCloud is defined', fakeAsync(() => {
getDefinitionsSpy = getDefinitionsSpy.and.returnValue(of([fakeProcessDefinitions[0]]));
formDefinitionSpy = spyOn(formCloudService, 'getForm').and.returnValue(of(fakeStartForm));
const change = new SimpleChange('myApp', 'myApp1', true);
component.ngOnInit();
component.ngOnChanges({ appName: change });
component.processForm.controls['processDefinition'].setValue('process');
fixture.detectChanges();
await fixture.whenStable();
await selectOptionByName('processwithform');
tick(3000);
component.processDefinitionName = fakeProcessDefinitions[0].name;
component.setProcessDefinitionOnForm(fakeProcessDefinitions[0].name);
fixture.detectChanges();
await fixture.whenStable();
component.processDefinitionName = fakeProcessDefinitions[2].name;
component.setProcessDefinitionOnForm(fakeProcessDefinitions[2].name);
fixture.detectChanges();
await fixture.whenStable();
tick(3000);
const processForm = fixture.nativeElement.querySelector('adf-cloud-form');
expect(component.hasForm()).toBeTruthy();
@@ -738,7 +766,7 @@ describe('StartProcessCloudComponent', () => {
const payload: ProcessPayloadCloud = new ProcessPayloadCloud({
name: component.processInstanceName.value,
processDefinitionKey: fakeProcessDefinitions[2].key,
processDefinitionKey: fakeProcessDefinitions[0].key,
variables: Object.assign({}, component.formCloud.values)
});
const startButton = fixture.debugElement.query(By.css('#button-start'));
@@ -746,7 +774,7 @@ describe('StartProcessCloudComponent', () => {
startButton.triggerEventHandler('click', null);
expect(startProcessSpy).toHaveBeenCalledWith(component.appName, payload);
});
}));
it('should output start event when process started successfully', () => {
const emitSpy = spyOn(component.success, 'emit');
@@ -830,18 +858,16 @@ describe('StartProcessCloudComponent', () => {
expect(startBtn.disabled).toBe(true);
});
it('should emit processDefinitionSelection event when a process definition is selected', async () => {
const emitSpy = spyOn(component.processDefinitionSelection, 'emit');
it('should emit processDefinitionSelection event when a process definition is selected', (done) => {
component.appName = 'myApp';
formDefinitionSpy.and.returnValue(of(fakeStartForm));
component.ngOnChanges({ appName: firstChange });
component.processDefinitionSelection.subscribe((processDefinition) => {
expect(processDefinition).toEqual(fakeProcessDefinitions[0]);
done();
});
fixture.detectChanges();
await fixture.whenStable();
component.processDefinitionName = 'processwithoutform1';
selectOptionByName(fakeProcessDefinitions[0].name);
fixture.detectChanges();
await fixture.whenStable();
expect(emitSpy).toHaveBeenCalledOnceWith(fakeProcessDefinitions[0]);
});
it('should wait for process definition to be loaded before showing the empty process definition message', () => {
@@ -857,18 +883,17 @@ describe('StartProcessCloudComponent', () => {
expect(noProcessElement).not.toBeNull();
});
it('should set the process name using the processName cloud pipe when a process definition gets selected', async () => {
it('should set the process name using the processName cloud pipe when a process definition gets selected', () => {
const processNameCloudPipe = TestBed.inject(ProcessNameCloudPipe);
const processNamePipeTransformSpy = spyOn(processNameCloudPipe, 'transform').and.returnValue('fake-transformed-name');
const expectedProcessInstanceDetails: ProcessInstanceCloud = { processDefinitionName: fakeProcessDefinitions[0].name };
getDefinitionsSpy.and.returnValue(of([fakeProcessDefinitions[0]]));
formDefinitionSpy.and.stub();
getDefinitionsSpy = getDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions));
component.appName = 'myApp';
component.ngOnChanges({ appName: firstChange });
fixture.detectChanges();
await fixture.whenStable();
selectOptionByName(fakeProcessDefinitions[0].name);
expect(processNamePipeTransformSpy).toHaveBeenCalledWith(component.name, expectedProcessInstanceDetails);
expect(component.processInstanceName.dirty).toBe(true);
@@ -977,7 +1002,6 @@ describe('StartProcessCloudComponent', () => {
beforeEach(() => {
component.name = 'NewProcess 1';
component.appName = 'myApp';
getDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions));
component.ngOnChanges({ appName: firstChange });
component.processDefinitionList = fakeProcessDefinitions;
component.processDefinitionName = fakeProcessDefinitions[0].name;
@@ -1016,7 +1040,6 @@ describe('StartProcessCloudComponent', () => {
beforeEach(() => {
component.name = 'NewProcess 1';
component.appName = 'myApp';
getDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions));
component.ngOnChanges({ appName: firstChange });
fixture.detectChanges();
});
@@ -80,7 +80,7 @@ describe('PreferenceService', () => {
});
});
it('Should not fetch preferences if error occurred', (done) => {
it('Should not fetch preferences if error occurred', () => {
getInstanceSpy.and.returnValue(apiErrorMock);
service.getPreferences('mock-app-name')
.subscribe(
@@ -89,7 +89,6 @@ describe('PreferenceService', () => {
expect(error.state).toEqual(404);
expect(error.stateText).toEqual('Not Found');
expect(error.error).toEqual('Mock Error');
done();
}
);
});
@@ -108,7 +107,7 @@ describe('PreferenceService', () => {
});
});
it('Should not fetch preference by key if error occurred', (done) => {
it('Should not fetch preference by key if error occurred', () => {
getInstanceSpy.and.returnValue(apiErrorMock);
service.getPreferenceByKey('mock-app-name', 'mock-preference-key')
.subscribe(
@@ -117,7 +116,6 @@ describe('PreferenceService', () => {
expect(error.state).toEqual(404);
expect(error.stateText).toEqual('Not Found');
expect(error.error).toEqual('Mock Error');
done();
}
);
});
@@ -134,7 +132,7 @@ describe('PreferenceService', () => {
});
});
it('Should not create preference if error occurred', (done) => {
it('Should not create preference if error occurred', () => {
getInstanceSpy.and.returnValue(apiErrorMock);
service.createPreference('mock-app-name', 'mock-preference-key', createMockPreference)
.subscribe(
@@ -143,7 +141,6 @@ describe('PreferenceService', () => {
expect(error.state).toEqual(404);
expect(error.stateText).toEqual('Not Found');
expect(error.error).toEqual('Mock Error');
done();
}
);
});
@@ -160,7 +157,7 @@ describe('PreferenceService', () => {
});
});
it('Should not update preference if error occurred', (done) => {
it('Should not update preference if error occurred', () => {
getInstanceSpy.and.returnValue(apiErrorMock);
service.createPreference('mock-app-name', 'mock-preference-key', updateMockPreference)
.subscribe(
@@ -169,7 +166,6 @@ describe('PreferenceService', () => {
expect(error.state).toEqual(404);
expect(error.stateText).toEqual('Not Found');
expect(error.error).toEqual('Mock Error');
done();
}
);
});
@@ -182,7 +178,7 @@ describe('PreferenceService', () => {
});
});
it('Should not delete preference if error occurred', (done) => {
it('Should not delete preference if error occurred', () => {
getInstanceSpy.and.returnValue(apiErrorMock);
service.deletePreference('mock-app-name', 'mock-preference-key')
.subscribe(
@@ -191,7 +187,6 @@ describe('PreferenceService', () => {
expect(error.state).toEqual(404);
expect(error.stateText).toEqual('Not Found');
expect(error.error).toEqual('Mock Error');
done();
}
);
});
@@ -1,33 +1,29 @@
<mat-nav-list class="adf-task-filters" *ngIf="filters$ | async as filterList; else loading">
<button
*ngFor="let filter of filterList"
mat-list-item
(click)="onFilterClick(filter)"
[attr.aria-label]="filter.name | translate"
[id]="filter.id"
[attr.data-automation-id]="filter.key + '_filter'"
[class.adf-active]="currentFilter === filter"
>
<div class="adf-task-filters__entry">
<div class="adf-task-filters__entry-label">
<adf-icon data-automation-id="adf-filter-icon"
*ngIf="showIcons"
[value]="filter.icon"
></adf-icon>
<span data-automation-id="adf-filter-label">
{{ filter.name | translate }}
</span>
</div>
<span *ngIf="counters$[filter.key]"
[attr.data-automation-id]="filter.key + '_filter-counter'"
class="adf-task-filters__entry-counter"
[class.adf-active]="wasFilterUpdated(filter.key)">
{{ counters$[filter.key] | async }}
</span>
</div>
<ng-container *ngIf="filters$ | async as filterList; else loading">
<div *ngFor="let filter of filterList"
class="adf-task-filters__entry">
<button (click)="onFilterClick(filter)"
[attr.aria-label]="filter.name | translate"
[id]="filter.id"
[attr.data-automation-id]="filter.key + '_filter'"
mat-button
[class.adf-active]="currentFilter === filter"
class="adf-filter-action-button adf-full-width"
fxLayout="row"
fxLayoutAlign="space-between center">
<adf-icon data-automation-id="adf-filter-icon"
*ngIf="showIcons"
[value]="filter.icon"></adf-icon>
<span data-automation-id="adf-filter-label"
class="adf-filter-action-button__label">{{ filter.name | translate }}</span>
</button>
</mat-nav-list>
<span *ngIf="counters$[filter.key]"
[attr.data-automation-id]="filter.key + '_filter-counter'"
class="adf-filter-action-button__counter"
[class.adf-active]=wasFilterUpdated(filter.key)>
{{ counters$[filter.key] | async }}
</span>
</div>
</ng-container>
<ng-template #loading>
<ng-container>
<div class="adf-app-list-spinner">
@@ -1,39 +1,58 @@
.adf-task-filters {
margin-right: calc(-1 * var(--adf-theme-spacing));
&__entry {
font-size: var(--theme-body-1-font-size);
color: var(--adf-theme-foreground-text-color-054);
.adf {
&-task-filters__entry {
display: flex;
justify-content: space-between;
align-items: center;
flex: 1;
height: 100%;
padding: 12px 0 !important;
height: 24px;
width: 100%;
font-size: var(--theme-body-1-font-size) !important;
font-weight: bold;
opacity: 1;
.adf-full-width {
display: flex;
width: 100%;
}
.adf-filter-action-button {
opacity: 0.54;
padding: 16px;
cursor: pointer;
.adf-filter-action-button__label {
padding-left: 20px;
margin: 0 8px !important;
}
}
.adf-filter-action-button__counter {
opacity: 0.54;
margin-left: 10px;
margin-top: 6px;
padding: 0 5px;
&.adf-active {
margin-left: 8px;
margin-top: 5px;
padding: 0 5px;
border-radius: 15px;
background-color: var(--theme-accent-color);
color: var(--adf-theme-mat-grey-color-50);
font-size: smaller;
}
}
&:hover {
color: var(--theme-primary-color);
.adf-filter-action-button__counter,
.adf-filter-action-button {
opacity: 1;
}
}
}
&__entry-label {
display: flex;
flex: 1;
align-items: center;
gap: var(--adf-theme-spacing);
}
&__entry-counter {
padding: 0 5px;
border-radius: 15px;
&.adf-active {
background-color: var(--theme-accent-color);
color: var(--adf-theme-mat-grey-color-50);
font-size: smaller;
.adf-active {
color: var(--theme-primary-color);
opacity: 1;
}
}
.adf-active .adf-task-filters__entry-label {
color: var(--theme-primary-color);
}
}
@@ -31,7 +31,6 @@ import { MatDialog } from '@angular/material/dialog';
import { IdentityUserModel } from '../../../../people/models/identity-user.model';
import { IdentityGroupModel } from '../../../../group/models/identity-group.model';
import { MatSelectChange } from '@angular/material/select';
import { Environment } from '../../../../common/interface/environment.interface';
/* eslint-disable @typescript-eslint/naming-convention */
@@ -68,10 +67,6 @@ export abstract class BaseEditTaskFilterCloudComponent<T> implements OnInit, OnC
@Input()
id: string;
/** List of environments. */
@Input()
environmentList: Environment[] = [];
/** processInstanceId of the task filter. */
@Input()
processInstanceId: string;
@@ -104,6 +99,14 @@ export abstract class BaseEditTaskFilterCloudComponent<T> implements OnInit, OnC
@Input()
sortProperties: string[] = [];
/** Task Filter to use*/
@Input()
taskFilter: T;
/** Emitted when a task filter property changes. */
@Output()
filterChange = new EventEmitter<T>();
/** Emitted when a filter action occurs (i.e Save, Save As, Delete). */
@Output()
action = new EventEmitter<TaskFilterAction>();
@@ -126,15 +129,8 @@ export abstract class BaseEditTaskFilterCloudComponent<T> implements OnInit, OnC
label: 'ADF_CLOUD_TASK_FILTERS.STATUS.ALL'
};
@Input()
taskFilter: T;
changedTaskFilter: T;
/** Emitted when a task filter property changes. */
@Output()
filterChange = new EventEmitter<T>();
protected onDestroy$ = new Subject<boolean>();
isLoading: boolean = false;
@@ -248,7 +244,7 @@ export abstract class BaseEditTaskFilterCloudComponent<T> implements OnInit, OnC
.subscribe((applications) => {
if (applications && applications.length > 0) {
applications.map((application) => {
this.applicationNames.push({ label: this.appsProcessCloudService.getApplicationLabel(application, this.environmentList), value: application.name });
this.applicationNames.push({ label: application.name, value: application.name });
});
}
});
@@ -15,18 +15,18 @@
* limitations under the License.
*/
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing';
import { SimpleChange } from '@angular/core';
import { By } from '@angular/platform-browser';
import { setupTestBed } from '@alfresco/adf-core';
import { MatDialog } from '@angular/material/dialog';
import { of, Subject } from 'rxjs';
import { of } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
import { TASK_FILTERS_SERVICE_TOKEN } from '../../../../services/cloud-token.service';
import { LocalPreferenceCloudService } from '../../../../services/local-preference-cloud.service';
import { ProcessServiceCloudTestingModule } from '../../../../testing/process-service-cloud.testing.module';
import { AppsProcessCloudService } from '../../../../app/services/apps-process-cloud.service';
import { fakeApplicationInstance, fakeApplicationInstanceWithEnvironment } from '../../../../app/mock/app-model.mock';
import { fakeApplicationInstance } from '../../../../app/mock/app-model.mock';
import { TaskFiltersCloudModule } from '../../task-filters-cloud.module';
import { ServiceTaskFilterCloudService } from '../../services/service-task-filter-cloud.service';
import { TaskCloudService } from '../../../services/task-cloud.service';
@@ -35,9 +35,6 @@ import { TranslateModule } from '@ngx-translate/core';
import { EditServiceTaskFilterCloudComponent } from './edit-service-task-filter-cloud.component';
import { MatIconTestingModule } from '@angular/material/icon/testing';
import { ProcessDefinitionCloud } from '../../../../models/process-definition-cloud.model';
import { TaskFilterDialogCloudComponent } from '../task-filter-dialog/task-filter-dialog-cloud.component';
import { fakeEnvironmentList } from '../../../../common/mock/environment.mock';
import { mockApplicationTaskFilterProperties } from '../../mock/edit-task-filter-cloud.mock';
describe('EditServiceTaskFilterCloudComponent', () => {
let component: EditServiceTaskFilterCloudComponent;
@@ -48,7 +45,6 @@ describe('EditServiceTaskFilterCloudComponent', () => {
let getTaskFilterSpy: jasmine.Spy;
let getRunningApplicationsSpy: jasmine.Spy;
let taskService: TaskCloudService;
const afterClosedSubject = new Subject<any>();
setupTestBed({
imports: [
@@ -70,10 +66,13 @@ describe('EditServiceTaskFilterCloudComponent', () => {
appsService = TestBed.inject(AppsProcessCloudService);
taskService = TestBed.inject(TaskCloudService);
dialog = TestBed.inject(MatDialog);
const dialogRefMock: any = {
afterClosed: () => afterClosedSubject
};
spyOn(dialog, 'open').and.returnValue(dialogRefMock);
spyOn(dialog, 'open').and.returnValue({
afterClosed: of({
action: EditServiceTaskFilterCloudComponent.ACTION_SAVE,
icon: 'icon',
name: 'fake-name'
})
} as any);
getTaskFilterSpy = spyOn(service, 'getTaskFilterById').and.returnValue(of(fakeServiceFilter));
getRunningApplicationsSpy = spyOn(appsService, 'getDeployedApplicationsByStatus').and.returnValue(of(fakeApplicationInstance));
fixture.detectChanges();
@@ -81,17 +80,18 @@ describe('EditServiceTaskFilterCloudComponent', () => {
afterEach(() => fixture.destroy());
it('should fetch task filter by taskId', async () => {
it('should fetch task filter by taskId', () => {
const taskFilterIdChange = new SimpleChange(undefined, 'mock-task-filter-id', true);
component.ngOnChanges({ id: taskFilterIdChange });
fixture.detectChanges();
await fixture.whenStable();
expect(getTaskFilterSpy).toHaveBeenCalled();
expect(component.taskFilter.name).toEqual('FakeInvolvedTasks');
expect(component.taskFilter.icon).toEqual('adjust');
expect(component.taskFilter.status).toEqual('COMPLETED');
expect(component.taskFilter.order).toEqual('ASC');
expect(component.taskFilter.sort).toEqual('id');
fixture.whenStable().then(() => {
expect(getTaskFilterSpy).toHaveBeenCalled();
expect(component.taskFilter.name).toEqual('FakeInvolvedTasks');
expect(component.taskFilter.icon).toEqual('adjust');
expect(component.taskFilter.status).toEqual('CREATED');
expect(component.taskFilter.order).toEqual('ASC');
expect(component.taskFilter.sort).toEqual('id');
});
});
it('should fetch process definitions when processDefinitionName filter property is set', async () => {
@@ -636,7 +636,7 @@ describe('EditServiceTaskFilterCloudComponent', () => {
spyOn(component.action, 'emit').and.callThrough();
});
it('should emit save event and save the filter on click save button', async () => {
it('should emit save event and save the filter on click save button', fakeAsync(() => {
component.toggleFilterActions = true;
spyOn(service, 'updateFilter').and.returnValue(of(null));
fixture.detectChanges();
@@ -650,15 +650,16 @@ describe('EditServiceTaskFilterCloudComponent', () => {
sortOptions[3].nativeElement.click();
fixture.detectChanges();
const saveButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-save"]');
await fixture.whenStable();
fixture.detectChanges();
expect(saveButton.disabled).toBe(false);
saveButton.click();
expect(service.updateFilter).toHaveBeenCalled();
expect(component.action.emit).toHaveBeenCalled();
});
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(saveButton.disabled).toBe(false);
saveButton.click();
expect(service.updateFilter).toHaveBeenCalled();
expect(component.action.emit).toHaveBeenCalled();
});
}));
it('should emit delete event and delete the filter on click of delete button', async () => {
it('should emit delete event and delete the filter on click of delete button', fakeAsync(() => {
component.toggleFilterActions = true;
spyOn(service, 'deleteFilter').and.returnValue(of(null));
fixture.detectChanges();
@@ -669,48 +670,40 @@ describe('EditServiceTaskFilterCloudComponent', () => {
stateElement.click();
fixture.detectChanges();
const deleteButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-delete"]');
await fixture.whenStable();
fixture.detectChanges();
expect(deleteButton.disabled).toBe(false);
deleteButton.click();
expect(service.deleteFilter).toHaveBeenCalled();
expect(component.action.emit).toHaveBeenCalled();
});
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(deleteButton.disabled).toBe(false);
deleteButton.click();
expect(service.deleteFilter).toHaveBeenCalled();
expect(component.action.emit).toHaveBeenCalled();
});
}));
it('should emit saveAs event and add filter on click saveAs button', async () => {
it('should emit saveAs event and add filter on click saveAs button', fakeAsync(() => {
component.toggleFilterActions = true;
spyOn(service, 'addFilter').and.returnValue(of(null));
fixture.detectChanges();
const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header');
expansionPanel.click();
fixture.detectChanges();
const stateElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-task-property-sort"] .mat-select-trigger');
stateElement.click();
const sortElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-task-property-sort"] .mat-select-trigger');
sortElement.click();
fixture.detectChanges();
const sortOptions = fixture.debugElement.queryAll(By.css('[data-automation-id="adf-cloud-edit-task-property-options-sort"] .mat-option-ripple'));
sortOptions[3].nativeElement.click();
const sortOptions = fixture.debugElement.queryAll(By.css('.mat-option-text'));
sortOptions[2].nativeElement.click();
fixture.detectChanges();
await fixture.whenStable();
const saveButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-saveAs"]');
saveButton.dispatchEvent(new Event('click'));
fixture.detectChanges();
afterClosedSubject.next({
action: TaskFilterDialogCloudComponent.ACTION_SAVE,
icon: 'icon',
name: 'fake-name'
const saveAsButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-saveAs"]');
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(saveAsButton.disabled).toBe(false);
saveAsButton.click();
expect(service.addFilter).toHaveBeenCalled();
expect(component.action.emit).toHaveBeenCalled();
expect(dialog.open).toHaveBeenCalled();
});
await fixture.whenStable();
}));
expect(service.addFilter).toHaveBeenCalled();
expect(component.action.emit).toHaveBeenCalled();
expect(dialog.open).toHaveBeenCalled();
});
it('should call restore default filters service on deletion of last filter', async () => {
it('should call restore default filters service on deletion of last filter', fakeAsync(() => {
component.toggleFilterActions = true;
spyOn(service, 'deleteFilter').and.returnValue(of([]));
const restoreDefaultFiltersSpy = spyOn(component, 'restoreDefaultTaskFilters').and.returnValue(of([]));
@@ -722,16 +715,17 @@ describe('EditServiceTaskFilterCloudComponent', () => {
stateElement.click();
fixture.detectChanges();
const deleteButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-delete"]');
await fixture.whenStable();
fixture.detectChanges();
expect(deleteButton.disabled).toBe(false);
deleteButton.click();
expect(service.deleteFilter).toHaveBeenCalled();
expect(component.action.emit).toHaveBeenCalled();
expect(restoreDefaultFiltersSpy).toHaveBeenCalled();
});
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(deleteButton.disabled).toBe(false);
deleteButton.click();
expect(service.deleteFilter).toHaveBeenCalled();
expect(component.action.emit).toHaveBeenCalled();
expect(restoreDefaultFiltersSpy).toHaveBeenCalled();
});
}));
it('should not call restore default filters service on deletion of first filter', async () => {
it('should not call restore default filters service on deletion of first filter', fakeAsync(() => {
component.toggleFilterActions = true;
spyOn(service, 'deleteFilter').and.returnValue(of([{ name: 'mock-filter-name' }]));
const restoreDefaultFiltersSpy = spyOn(component, 'restoreDefaultTaskFilters').and.returnValue(of([]));
@@ -743,24 +737,14 @@ describe('EditServiceTaskFilterCloudComponent', () => {
stateElement.click();
fixture.detectChanges();
const deleteButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-delete"]');
await fixture.whenStable();
fixture.detectChanges();
expect(deleteButton.disabled).toBe(false);
deleteButton.click();
expect(service.deleteFilter).toHaveBeenCalled();
expect(component.action.emit).toHaveBeenCalled();
expect(restoreDefaultFiltersSpy).not.toHaveBeenCalled();
});
});
it('should add environment name to each application selector option label', () => {
component.appName = fakeApplicationInstance[0].name;
component.environmentList = fakeEnvironmentList;
getRunningApplicationsSpy.and.returnValue(of(fakeApplicationInstanceWithEnvironment));
spyOn(component, 'createTaskFilterProperties').and.returnValue(mockApplicationTaskFilterProperties);
const filteredProperties = component.createAndFilterProperties();
expect(filteredProperties[0].options[0].label).toBe('application-new-1 (test-env-name-1)');
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(deleteButton.disabled).toBe(false);
deleteButton.click();
expect(service.deleteFilter).toHaveBeenCalled();
expect(component.action.emit).toHaveBeenCalled();
expect(restoreDefaultFiltersSpy).not.toHaveBeenCalled();
});
}));
});
});
@@ -15,12 +15,12 @@
* limitations under the License.
*/
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { AlfrescoApiService, setupTestBed } from '@alfresco/adf-core';
import { MatDialog } from '@angular/material/dialog';
import { of, Subject } from 'rxjs';
import { of } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
import { TASK_FILTERS_SERVICE_TOKEN } from '../../../../services/cloud-token.service';
import { LocalPreferenceCloudService } from '../../../../services/local-preference-cloud.service';
@@ -54,8 +54,6 @@ import {
} from '../../mock/edit-task-filter-cloud.mock';
import { mockFoodUsers } from '../../../../people/mock/people-cloud.mock';
import { mockFoodGroups } from '../../../../group/mock/group-cloud.mock';
import { SimpleChanges } from '@angular/core';
import { TaskFilterDialogCloudComponent } from '../task-filter-dialog/task-filter-dialog-cloud.component';
describe('EditTaskFilterCloudComponent', () => {
let component: EditTaskFilterCloudComponent;
@@ -67,7 +65,6 @@ describe('EditTaskFilterCloudComponent', () => {
let getTaskFilterSpy: jasmine.Spy;
let getRunningApplicationsSpy: jasmine.Spy;
let taskService: TaskCloudService;
const afterClosedSubject = new Subject<any>();
setupTestBed({
imports: [
@@ -91,10 +88,13 @@ describe('EditTaskFilterCloudComponent', () => {
taskService = TestBed.inject(TaskCloudService);
alfrescoApiService = TestBed.inject(AlfrescoApiService);
dialog = TestBed.inject(MatDialog);
const dialogRefMock: any = {
afterClosed: () => afterClosedSubject
};
spyOn(dialog, 'open').and.returnValue(dialogRefMock);
spyOn(dialog, 'open').and.returnValue({
afterClosed: of({
action: EditTaskFilterCloudComponent.ACTION_SAVE,
icon: 'icon',
name: 'fake-name'
})
} as any);
spyOn(alfrescoApiService, 'getInstance').and.returnValue(mockAlfrescoApi);
getTaskFilterSpy = spyOn(service, 'getTaskFilterById').and.returnValue(of(fakeFilter));
getRunningApplicationsSpy = spyOn(appsService, 'getDeployedApplicationsByStatus').and.returnValue(of(fakeApplicationInstance));
@@ -103,17 +103,17 @@ describe('EditTaskFilterCloudComponent', () => {
afterEach(() => fixture.destroy());
it('should fetch task filter by taskId', async () => {
it('should fetch task filter by taskId', () => {
component.ngOnChanges({ id: mockTaskFilterIdChange });
fixture.detectChanges();
await fixture.whenStable();
expect(getTaskFilterSpy).toHaveBeenCalled();
expect(component.taskFilter.name).toEqual('FakeInvolvedTasks');
expect(component.taskFilter.icon).toEqual('adjust');
expect(component.taskFilter.status).toEqual(TaskStatusFilter.CREATED);
expect(component.taskFilter.order).toEqual('ASC');
expect(component.taskFilter.sort).toEqual('id');
fixture.whenStable().then(() => {
expect(getTaskFilterSpy).toHaveBeenCalled();
expect(component.taskFilter.name).toEqual('FakeInvolvedTasks');
expect(component.taskFilter.icon).toEqual('adjust');
expect(component.taskFilter.status).toEqual(TaskStatusFilter.CREATED);
expect(component.taskFilter.order).toEqual('ASC');
expect(component.taskFilter.sort).toEqual('id');
});
});
describe('processInstanceId filter', () => {
@@ -192,6 +192,7 @@ describe('EditTaskFilterCloudComponent', () => {
expect(getProcessInstanceIdInputElement().value).toEqual('fakeProcessInstanceIdFromResponse');
});
});
it('should fetch process definitions when processDefinitionName filter property is set', async () => {
@@ -955,39 +956,36 @@ describe('EditTaskFilterCloudComponent', () => {
describe('edit filter actions', () => {
beforeEach(() => {
component.changedTaskFilter = { name: 'mock-filter-name' } as TaskFilterCloudModel;
component.ngOnChanges({ id: mockTaskFilterIdChange } as SimpleChanges);
spyOn(component.action, 'emit').and.stub();
component.toggleFilterActions = true;
component.ngOnChanges({ id: mockTaskFilterIdChange });
fixture.detectChanges();
spyOn(component.action, 'emit').and.callThrough();
});
it('should emit save event and save the filter on click save button', fakeAsync(() => {
spyOn(service, 'updateFilter').and.returnValue(of([new TaskFilterCloudModel({ name: 'mock-filter-name' })]));
component.toggleFilterActions = true;
spyOn(service, 'updateFilter').and.returnValue(of(null));
fixture.detectChanges();
const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header');
expansionPanel.click();
fixture.detectChanges();
tick();
const stateElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-task-property-sort"] .mat-select-trigger');
stateElement.click();
fixture.detectChanges();
tick();
const sortOptions = fixture.debugElement.queryAll(By.css('[data-automation-id="adf-cloud-edit-task-property-options-sort"] .mat-option-ripple'));
const sortOptions = fixture.debugElement.queryAll(By.css('.mat-option-text'));
sortOptions[3].nativeElement.click();
fixture.detectChanges();
tick(550);
const saveButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-save"]');
saveButton.dispatchEvent(new Event('click'));
fixture.detectChanges();
tick();
expect(service.updateFilter).toHaveBeenCalled();
expect(component.action.emit).toHaveBeenCalled();
fixture.whenStable().then(() => {
const saveButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-save"]');
expect(saveButton.disabled).toBe(false);
saveButton.click();
expect(service.updateFilter).toHaveBeenCalled();
expect(component.action.emit).toHaveBeenCalled();
});
}));
it('should emit delete event and delete the filter on click of delete button', async () => {
component.toggleFilterActions = true;
spyOn(service, 'deleteFilter').and.returnValue(of(null));
fixture.detectChanges();
const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header');
@@ -1000,45 +998,38 @@ describe('EditTaskFilterCloudComponent', () => {
await fixture.whenStable();
const deleteButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-delete"]');
expect(deleteButton.getAttribute('disabled')).toBeNull();
expect(deleteButton.disabled).toBe(false);
deleteButton.click();
expect(service.deleteFilter).toHaveBeenCalled();
expect(component.action.emit).toHaveBeenCalled();
});
it('should emit saveAs event and add filter on click saveAs button', async () => {
it('should emit saveAs event and add filter on click saveAs button', fakeAsync(() => {
component.toggleFilterActions = true;
spyOn(service, 'addFilter').and.returnValue(of(null));
fixture.detectChanges();
const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header');
expansionPanel.click();
fixture.detectChanges();
const stateElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-task-property-sort"] .mat-select-trigger');
stateElement.click();
const sortElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-task-property-sort"] .mat-select-trigger');
sortElement.click();
fixture.detectChanges();
const sortOptions = fixture.debugElement.queryAll(By.css('.mat-option-text'));
sortOptions[2].nativeElement.click();
const sortOptions = fixture.debugElement.queryAll(By.css('[data-automation-id="adf-cloud-edit-task-property-options-sort"] .mat-option-ripple'));
sortOptions[3].nativeElement.click();
fixture.detectChanges();
await fixture.whenStable();
const saveButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-saveAs"]');
saveButton.dispatchEvent(new Event('click'));
fixture.detectChanges();
afterClosedSubject.next({
action: TaskFilterDialogCloudComponent.ACTION_SAVE,
icon: 'icon',
name: 'fake-name'
fixture.whenStable().then(() => {
const saveAsButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-saveAs"]');
expect(saveAsButton.disabled).toBe(false);
saveAsButton.click();
expect(service.addFilter).toHaveBeenCalled();
expect(component.action.emit).toHaveBeenCalled();
expect(dialog.open).toHaveBeenCalled();
});
await fixture.whenStable();
expect(service.addFilter).toHaveBeenCalled();
expect(component.action.emit).toHaveBeenCalled();
expect(dialog.open).toHaveBeenCalled();
});
}));
it('should call restore default filters service on deletion of last filter', async () => {
component.toggleFilterActions = true;
spyOn(service, 'deleteFilter').and.returnValue(of([]));
const restoreDefaultFiltersSpy = spyOn(component, 'restoreDefaultTaskFilters').and.returnValue(of([]));
fixture.detectChanges();
@@ -1060,6 +1051,7 @@ describe('EditTaskFilterCloudComponent', () => {
});
it('should not call restore default filters service on deletion of first filter', async () => {
component.toggleFilterActions = true;
spyOn(service, 'deleteFilter').and.returnValue(of([new TaskFilterCloudModel({ name: 'mock-filter-name' })]));
const restoreDefaultFiltersSpy = spyOn(component, 'restoreDefaultTaskFilters').and.returnValue(of([]));
fixture.detectChanges();
@@ -98,7 +98,7 @@ export class EditTaskFilterCloudComponent extends BaseEditTaskFilterCloudCompone
}
}
protected updateFilter(filterToUpdate: TaskFilterCloudModel): Observable<TaskFilterCloudModel[]> {
protected updateFilter(filterToUpdate: TaskFilterCloudModel): Observable<any> {
return this.taskFilterCloudService.updateFilter(filterToUpdate);
}
@@ -276,7 +276,7 @@ describe('TaskFiltersCloudComponent', () => {
fixture.detectChanges();
await fixture.whenStable();
const filterCounters = fixture.debugElement.queryAll(By.css('.adf-task-filters__entry-counter'));
const filterCounters = fixture.debugElement.queryAll(By.css('.adf-filter-action-button__counter'));
expect(component.filters.length).toBe(3);
expect(filterCounters.length).toBe(1);
expect(filterCounters[0].nativeElement.innerText).toContain('11');
@@ -108,28 +108,3 @@ export const mockCreatedDateFilter = {
to: '_createdTo'
}
};
export const mockApplicationsSelectorOptions = [
{
label: 'application-new-1 (test-env-name-1)',
value: 'application-new-1'
},
{
label: 'application-new-1 (test-env-name-1)',
value: 'application-new-1'
},
{
label: 'application-new-1 (test-env-name-1)',
value: 'application-new-1'
}
];
export const mockApplicationTaskFilterProperties = [
{
label: 'test-label',
type: 'select',
key: 'appName',
value: mockApplicationsSelectorOptions[0].value,
options: mockApplicationsSelectorOptions
}
];
@@ -29,7 +29,6 @@ export class TaskFilterCloudModel {
id: string;
name: string;
key: string;
environmentId?: string;
icon: string;
index: number;
appName: string;
@@ -73,7 +72,6 @@ export class TaskFilterCloudModel {
this.id = obj.id || Math.random().toString(36).substr(2, 9);
this.name = obj.name || null;
this.key = obj.key || null;
this.environmentId = obj.environmentId || null;
this.icon = obj.icon || null;
this.index = obj.index || null;
this.appName = obj.appName || obj.appName === '' ? obj.appName : null;
@@ -193,7 +191,6 @@ export class TaskFilterCloudModel {
export interface ServiceTaskFilterCloudModel {
id?: string;
name?: string;
environmentId?: string;
key?: string;
icon?: string;
index?: number;
@@ -85,10 +85,6 @@ describe('TaskFormCloudComponent', () => {
component = fixture.componentInstance;
});
afterEach(() => {
fixture.destroy();
});
describe('Complete button', () => {
beforeEach(() => {
@@ -289,62 +285,57 @@ describe('TaskFormCloudComponent', () => {
beforeEach(() => {
component.appName = 'app1';
component.taskId = 'task1';
fixture.detectChanges();
});
it('should emit cancelClick when cancel button is clicked', async () => {
spyOn(component.cancelClick,'emit').and.stub();
it('should emit cancelClick when cancel button is clicked', (done) => {
component.cancelClick.subscribe(() => {
done();
});
fixture.detectChanges();
const cancelBtn = debugElement.query(By.css('#adf-cloud-cancel-task'));
cancelBtn.triggerEventHandler('click', {});
fixture.detectChanges();
await fixture.whenStable();
expect(component.cancelClick.emit).toHaveBeenCalledOnceWith('task1');
cancelBtn.nativeElement.click();
});
it('should emit taskCompleted when task is completed', async () => {
it('should emit taskCompleted when task is completed', (done) => {
spyOn(taskCloudService, 'completeTask').and.returnValue(of({}));
spyOn(component.taskCompleted, 'emit').and.stub();
component.taskCompleted.subscribe(() => {
done();
});
component.ngOnChanges({ appName: new SimpleChange(null, 'app1', false) });
fixture.detectChanges();
const completeBtn = debugElement.query(By.css('[adf-cloud-complete-task]'));
completeBtn.triggerEventHandler('click', {});
fixture.detectChanges();
await fixture.whenStable();
expect(component.taskCompleted.emit).toHaveBeenCalledOnceWith('task1');
completeBtn.nativeElement.click();
});
it('should emit taskClaimed when task is claimed', async () => {
it('should emit taskClaimed when task is claimed', (done) => {
spyOn(taskCloudService, 'claimTask').and.returnValue(of({}));
spyOn(component, 'hasCandidateUsers').and.returnValue(true);
spyOn(component.taskClaimed, 'emit').and.stub();
taskDetails.status = TASK_CREATED_STATE;
taskDetails.permissions = [TASK_CLAIM_PERMISSION];
getTaskSpy.and.returnValue(of(taskDetails));
component.taskClaimed.subscribe(() => {
done();
});
component.ngOnChanges({ appName: new SimpleChange(null, 'app1', false) });
fixture.detectChanges();
const claimBtn = debugElement.query(By.css('[adf-cloud-claim-task]'));
claimBtn.triggerEventHandler('click', {});
fixture.detectChanges();
await fixture.whenStable();
expect(component.taskClaimed.emit).toHaveBeenCalledOnceWith('task1');
claimBtn.nativeElement.click();
});
it('should emit error when error occurs', async () => {
spyOn(component.error, 'emit').and.stub();
it('should emit error when error occurs', (done) => {
component.error.subscribe(() => {
done();
});
component.onError({});
fixture.detectChanges();
await fixture.whenStable();
expect(component.error.emit).toHaveBeenCalled();
});
it('should reload when task is completed', () => {
@@ -81,7 +81,6 @@ export class ServiceTaskListCloudComponent extends BaseTaskListCloudComponent {
skipCount: this.skipCount,
sorting: this.sorting,
id: this.queryParams?.serviceTaskId,
environmentId: this.queryParams?.environmentId,
activityName: this.queryParams?.activityName,
activityType: this.queryParams?.activityType,
completedDate: this.queryParams?.completedDate,
@@ -16,20 +16,9 @@
*/
import { Component, SimpleChange, ViewChild } from '@angular/core';
import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
import { ComponentFixture, TestBed, fakeAsync } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { AppConfigService,
setupTestBed,
DataRowEvent,
ObjectDataRow,
User,
DataColumn,
ColumnsSelectorComponent,
AlfrescoApiService,
AlfrescoApiServiceMock,
AppConfigServiceMock,
TranslationService,
TranslationMock } from '@alfresco/adf-core';
import { AppConfigService, setupTestBed, DataRowEvent, ObjectDataRow, User, DataColumn, ColumnsSelectorComponent } from '@alfresco/adf-core';
import { TaskListCloudService } from '../services/task-list-cloud.service';
import { TaskListCloudComponent } from './task-list-cloud.component';
import { fakeGlobalTasks, fakeCustomSchema, fakeGlobalTask } from '../mock/fake-task-response.mock';
@@ -40,9 +29,6 @@ import { TaskListCloudSortingModel } from '../../../models/task-list-sorting.mod
import { shareReplay, skip } from 'rxjs/operators';
import { TaskListCloudServiceInterface } from '../../../services/task-list-cloud.service.interface';
import { TASK_LIST_CLOUD_TOKEN } from '../../../services/cloud-token.service';
import { TaskListCloudModule } from '../task-list-cloud.module';
import { HttpClientModule } from '@angular/common/http';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
@Component({
template: `
@@ -102,6 +88,9 @@ describe('TaskListCloudComponent', () => {
TranslateModule.forRoot(),
ProcessServiceCloudTestingModule
],
declarations: [
EmptyTemplateComponent
],
providers: [
{
provide: TASK_LIST_CLOUD_TOKEN,
@@ -534,20 +523,6 @@ describe('TaskListCloudComponent', () => {
describe('Creating an empty custom template - EmptyTemplateComponent', () => {
let fixtureEmpty: ComponentFixture<EmptyTemplateComponent>;
setupTestBed({
imports: [
HttpClientModule,
NoopAnimationsModule,
TranslateModule.forRoot(),
TaskListCloudModule
],
providers: [
{ provide: AlfrescoApiService, useClass: AlfrescoApiServiceMock },
{ provide: AppConfigService, useClass: AppConfigServiceMock },
{ provide: TranslationService, useClass: TranslationMock }
]
});
beforeEach(() => {
const emptyList = { list: { entries: [] } };
spyOn(taskListCloudService, 'getTaskByRequest').and.returnValue(of(emptyList));
@@ -560,12 +535,16 @@ describe('TaskListCloudComponent', () => {
fixtureEmpty.destroy();
});
it('should render the custom template', async () => {
// TODO still not working because of the Loading Spinner
// eslint-disable-next-line
xit('should render the custom template', (done) => {
fixtureEmpty.detectChanges();
await fixtureEmpty.whenStable();
fixtureEmpty.detectChanges();
expect(fixtureEmpty.debugElement.query(By.css('#custom-id'))).not.toBeNull();
expect(fixtureEmpty.debugElement.query(By.css('.adf-empty-content'))).toBeNull();
fixtureEmpty.whenStable().then(() => {
fixtureEmpty.detectChanges();
expect(fixtureEmpty.debugElement.query(By.css('#custom-id'))).not.toBeNull();
expect(fixtureEmpty.debugElement.query(By.css('.adf-empty-content'))).toBeNull();
done();
});
});
});
@@ -602,7 +581,7 @@ describe('TaskListCloudComponent', () => {
sortable: true
},
{
key: 'priority',
key: 'entry.priority',
type: 'text',
title: 'ADF_TASK_LIST.PROPERTIES.PRIORITY',
sortable: true
@@ -623,34 +602,45 @@ describe('TaskListCloudComponent', () => {
fixture.destroy();
});
it('should show tooltip if config copyContent flag is true', fakeAsync(() => {
// TODO: highly unstable test
// eslint-disable-next-line
xit('should show tooltip if config copyContent flag is true', fakeAsync(() => {
taskSpy.and.returnValue(of(fakeGlobalTasks));
const appName = new SimpleChange(null, 'FAKE-APP-NAME', true);
component.success.subscribe(() => {
fixture.whenStable().then(() => {
fixture.detectChanges();
const spanHTMLElement = element.querySelector<HTMLInputElement>('span[title="11fe013d-c263-11e8-b75b-0a5864600540"]');
spanHTMLElement.dispatchEvent(new Event('mouseenter'));
fixture.detectChanges();
expect(fixture.debugElement.nativeElement.querySelector('.adf-copy-tooltip')).not.toBeNull();
});
});
component.presetColumn = 'fakeCustomSchema';
component.appName = appName.currentValue;
component.ngOnChanges({ appName });
component.ngAfterContentInit();
tick();
fixture.detectChanges();
const spanHTMLElement = element.querySelector<HTMLInputElement>('span[title="11fe013d-c263-11e8-b75b-0a5864600540"]');
spanHTMLElement.dispatchEvent(new Event('mouseenter'));
fixture.detectChanges();
expect(fixture.debugElement.nativeElement.querySelector('.adf-copy-tooltip')).not.toBeNull();
}));
it('should replace priority values', fakeAsync(() => {
// TODO: highly unstable test
// eslint-disable-next-line
xit('should replace priority values', (done) => {
taskSpy.and.returnValue(of(fakeGlobalTasks));
component.presetColumn = 'fakeCustomSchema';
const appName = new SimpleChange(null, 'FAKE-APP-NAME', true);
component.ngOnChanges({ appName });
component.success.subscribe(() => {
const cell = fixture.nativeElement.querySelector('[data-automation-id="text_ADF_CLOUD_TASK_LIST.PROPERTIES.PRIORITY_VALUES.NONE"]');
expect(cell.textContent).toEqual('ADF_CLOUD_TASK_LIST.PROPERTIES.PRIORITY_VALUES.NONE');
done();
});
fixture.detectChanges();
component.reload();
tick();
const cell = fixture.nativeElement.querySelector('[data-automation-id="text_ADF_CLOUD_TASK_LIST.PROPERTIES.PRIORITY_VALUES.NONE"]');
expect(cell.textContent).toEqual('ADF_CLOUD_TASK_LIST.PROPERTIES.PRIORITY_VALUES.NONE');
}));
});
it('replacePriorityValues should return undefined when no rows defined', () => {
const emptyList = { list: { entries: [] } };
@@ -89,10 +89,6 @@ export class TaskListCloudComponent extends BaseTaskListCloudComponent<ProcessLi
@Input()
name: string = '';
/** Filter the tasks to display only the ones with this environment ID. */
@Input()
environmentId: string;
/** Filter the tasks. Display only tasks with parentTaskId equal to the supplied value. */
@Input()
parentTaskId: string = '';
@@ -194,7 +190,6 @@ export class TaskListCloudComponent extends BaseTaskListCloudComponent<ProcessLi
assignee: this.assignee,
id: this.id,
name: this.name,
environmentId: this.environmentId,
parentTaskId: this.parentTaskId,
processDefinitionName: this.processDefinitionName,
processDefinitionId: this.processDefinitionId,
@@ -20,7 +20,6 @@ import { TaskListCloudSortingModel } from '../../../models/task-list-sorting.mod
export interface ServiceTaskQueryCloudRequestModel {
appName: string;
appVersion?: string;
environmentId?: string;
id?: string;
status?: string;
maxItems: number;
@@ -132,43 +132,39 @@ describe('Activiti ServiceTaskList Cloud Service', () => {
});
it('should throw an exeption and execute logService error if appName is null', (done) => {
it('should throw an exeption and execute logService error if appName is null', () => {
const expectedErrorMessage = 'Appname/executionId/flowNodeId not configured';
const params = [null, 'executionId_1', 'flowNodeId_1'] as const;
service.replayServiceTaskRequest(...params).toPromise().catch((error) => {
expect(error).toEqual(expectedErrorMessage);
expect(logServiceErrorSpy).toHaveBeenCalled();
done();
});
});
it('should throw an exeption and execute logService error if executionId is null', (done) => {
it('should throw an exeption and execute logService error if executionId is null', () => {
const expectedErrorMessage = 'Appname/executionId/flowNodeId not configured';
const params = ['fakeName', null, 'flowNodeId_1'] as const;
service.replayServiceTaskRequest(...params).toPromise().catch((error) => {
expect(error).toEqual(expectedErrorMessage);
expect(logServiceErrorSpy).toHaveBeenCalled();
done();
});
});
it('should throw an exeption and execute logService error if flowNodeId is null', (done) => {
it('should throw an exeption and execute logService error if flowNodeId is null', () => {
const expectedErrorMessage = 'Appname/executionId/flowNodeId not configured';
const params = ['fakeName', 'executionId_1', null] as const;
service.replayServiceTaskRequest(...params).toPromise().catch((error) => {
expect(error).toEqual(expectedErrorMessage);
expect(logServiceErrorSpy).toHaveBeenCalled();
done();
});
});
it('should throw an exeption and execute logService error if appName, executionId and flowNodeId are null', (done) => {
it('should throw an exeption and execute logService error if appName, executionId and flowNodeId are null', () => {
const expectedErrorMessage = 'Appname/executionId/flowNodeId not configured';
const params = [null, null, null] as const;
service.replayServiceTaskRequest(...params).toPromise().catch((error) => {
expect(error).toEqual(expectedErrorMessage);
expect(logServiceErrorSpy).toHaveBeenCalled();
done();
});
});
});
@@ -26,7 +26,6 @@ export * from './lib/services/public-api';
export * from './lib/rich-text-editor/public-api';
export * from './lib/types';
export * from './lib/common/index';
export * from './lib/pipes/process-name-cloud.pipe';
export * from './lib/pipes/process-services-cloud-pipe.module';
export * from './lib/models/process-definition-cloud.model';
@@ -20,7 +20,7 @@ import { BrowserActions } from '../utils/browser-actions';
export class BreadcrumbPage {
breadcrumb = $(`adf-breadcrumb nav[data-automation-id='breadcrumb_break_it']`);
breadcrumb = $(`adf-breadcrumb nav[data-automation-id='breadcrumb']`);
currentItem = $('.adf-breadcrumb-item-current');
async chooseBreadCrumb(breadCrumbItem: string): Promise<void> {
@@ -26,19 +26,19 @@ export interface LoginOptions {
}
export class LoginPage {
loginUrl = `${browser.baseUrl}/login/break-all-e2es`;
loginUrl = `${browser.baseUrl}/login`;
ssoButton = $(`[data-automation-id="login-button-sso-break-it"]`);
usernameField = $('#username_break_it');
passwordField = $('#password_break_it');
loginButton = $('input[type="submit_break_it"]');
userIcon = $(`[data-automation-id*='user-initials_break_it']`);
loginError = $(`div[data-automation-id="login-error_break_it"]`);
ssoButton = $(`[data-automation-id="login-button-sso"]`);
usernameField = $('#username');
passwordField = $('#password');
loginButton = $('input[type="submit"]');
userIcon = $(`[data-automation-id*='user-initials']`);
loginError = $(`div[data-automation-id="login-error"]`);
visibilityLabel = $('#v');
txtUsernameBasicAuth = $('input[id="username_break_it"]');
txtPasswordBasicAuth = $('input[id="password_break_it"]');
signInButtonBasicAuth = $('#login-button_break_it');
txtUsernameBasicAuth = $('input[id="username"]');
txtPasswordBasicAuth = $('input[id="password"]');
signInButtonBasicAuth = $('#login-button');
async goToLoginPage(): Promise<void> {
let currentUrl;
@@ -22,27 +22,27 @@ import { Logger } from '../utils/logger';
export class PaginationPage {
pageSelectorDropDown = $('div[class*="adf-pagination__page-selector_break_it"]');
pageSelectorArrow = $('button[data-automation-id="page-selector_break_it"]');
itemsPerPage = $('.adf-pagination__max-items_break_it');
itemsPerPageOpenDropdown = $('.adf-pagination__perpage-block button_break_it');
itemsPerPageOptions = $$('.adf-pagination__page-selector .mat-menu-item_break_it');
currentPage = $('.adf-pagination__current-page_break_it');
totalPages = $('.adf-pagination__total-pages_break_it');
paginationRange = $('.adf-pagination__range_break_it');
nextPageButton = $('button[class*="adf-pagination__next-button_break_it"]');
nextButtonDisabled = $('button[class*="adf-pagination__next-button_break_it"][disabled]');
previousButtonDisabled = $('button[class*="adf-pagination__previous-button_break_it"][disabled]');
pageDropDown = $('div[class*="adf-pagination__actualinfo-block_break_it"] button');
pageDropDownOptions = $$('div[class*="mat-menu-content_break_it"] button');
paginationSection = $('adf-pagination_break_it');
paginationSectionEmpty = $('adf-pagination[class*="adf-pagination__empty_break_it"]');
totalFiles = $('.adf-pagination__range_break_it');
pageSelectorDropDown = $('div[class*="adf-pagination__page-selector"]');
pageSelectorArrow = $('button[data-automation-id="page-selector"]');
itemsPerPage = $('.adf-pagination__max-items');
itemsPerPageOpenDropdown = $('.adf-pagination__perpage-block button');
itemsPerPageOptions = $$('.adf-pagination__page-selector .mat-menu-item');
currentPage = $('.adf-pagination__current-page');
totalPages = $('.adf-pagination__total-pages');
paginationRange = $('.adf-pagination__range');
nextPageButton = $('button[class*="adf-pagination__next-button"]');
nextButtonDisabled = $('button[class*="adf-pagination__next-button"][disabled]');
previousButtonDisabled = $('button[class*="adf-pagination__previous-button"][disabled]');
pageDropDown = $('div[class*="adf-pagination__actualinfo-block"] button');
pageDropDownOptions = $$('div[class*="mat-menu-content"] button');
paginationSection = $('adf-pagination');
paginationSectionEmpty = $('adf-pagination[class*="adf-pagination__empty"]');
totalFiles = $('.adf-pagination__range');
async selectItemsPerPage(numberOfItem: string): Promise<void> {
await BrowserActions.clickExecuteScript(`div[class*="adf-pagination__perpage-block_break_it"] button`);
await BrowserActions.clickExecuteScript(`div[class*="adf-pagination__perpage-block"] button`);
await BrowserVisibility.waitUntilElementIsVisible(this.pageSelectorDropDown);
const itemsPerPage = element.all(by.cssContainingText('.mat-menu-item_break_it', numberOfItem)).first();
const itemsPerPage = element.all(by.cssContainingText('.mat-menu-item', numberOfItem)).first();
await BrowserVisibility.waitUntilElementIsPresent(itemsPerPage);
await BrowserActions.click(itemsPerPage);
await BrowserVisibility.waitUntilElementIsNotVisible(this.pageSelectorDropDown);
+529 -637
View File
File diff suppressed because it is too large Load Diff
+5 -5
View File
@@ -78,7 +78,7 @@
"@storybook/core-server": "6.4.22",
"angular-oauth2-oidc": "^13.0.1",
"angular-oauth2-oidc-jwks": "^15.0.1",
"apollo-angular": "^4.2.1",
"apollo-angular": "^4.0.1",
"chart.js": "2.9.4",
"cropperjs": "1.5.13",
"dotenv-expand": "^5.1.0",
@@ -98,7 +98,7 @@
"devDependencies": {
"@angular-devkit/build-angular": "14.1.3",
"@angular-devkit/schematics": "~14.1.0",
"@angular-eslint/eslint-plugin": "15.2.1",
"@angular-eslint/eslint-plugin": "14.0.4",
"@angular-eslint/eslint-plugin-template": "14.3.0",
"@angular-eslint/template-parser": "14.0.2",
"@angular/cli": "~14.1.0",
@@ -126,7 +126,7 @@
"@types/pdfjs-dist": "^2.10.378",
"@types/request": "^2.48.5",
"@types/selenium-webdriver": "^4.0.11",
"@typescript-eslint/eslint-plugin": "5.57.0",
"@typescript-eslint/eslint-plugin": "5.47.0",
"@typescript-eslint/parser": "5.38.1",
"@typescript-eslint/typescript-estree": "5.55.0",
"commander": "6.2.1",
@@ -140,7 +140,7 @@
"eslint-plugin-jsdoc": "40.1.0",
"eslint-plugin-license-header": "0.6.0",
"eslint-plugin-prefer-arrow": "1.2.3",
"eslint-plugin-rxjs": "^5.0.3",
"eslint-plugin-rxjs": "^5.0.2",
"eslint-plugin-unicorn": "^46.0.0",
"github-api": "^3.4.0",
"graphql": "^16.6.0",
@@ -172,7 +172,7 @@
"protractor-retry-angular-cli": "^2.0.3",
"protractor-screenshoter-plugin": "0.10.3",
"protractor-smartrunner": "^2.0.0-beta6",
"rimraf": "^4.4.1",
"rimraf": "^4.4.0",
"sass-loader": "10.1.1",
"selenium-webdriver": "4.1.0",
"stylelint": "^14.15.0",
+5 -20
View File
@@ -2,7 +2,6 @@
set -e
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
verifyLib=$1;
deps=$2;
cd $DIR/../../
if [ "${GITHUB_EVENT_NAME}" == "schedule" ]; then
@@ -10,27 +9,13 @@ if [ "${GITHUB_EVENT_NAME}" == "schedule" ]; then
echo true
exit 0
fi
isAffected=false
AFFECTED_LIBS=$(npx nx print-affected --type=lib --select=projects ${NX_CALCULATION_FLAGS} --plain)
#echo "Verify if affected build contains $1"
#echo "Affected libs:$AFFECTED_LIBS"
if [[ $AFFECTED_LIBS =~ $verifyLib ]]; then
#echo "Yep project:$verifyLib is affected carry on"
isAffected=true
#echo "Yep project:$verifyLib is affected carry on"
echo true
else
#echo "Nope project NOT affected save time"
echo false
fi;
if [[ $isAffected == false ]]; then
# echo "Read the deps as array"
IFS=',' read -ra depsArray <<< "$deps"
#echo "Loop through the deps to check if they are affected"
for i in "${depsArray[@]}"
do
if [[ $AFFECTED_LIBS =~ $i ]]; then
isAffected=true;
break
fi;
done;
fi;
echo $isAffected;
+6 -7
View File
@@ -7,22 +7,21 @@ cd $DIR/../../../
BASE_DIRECTORY=$(echo "$FOLDER" | cut -d "/" -f1)
verifyLib=$1;
deps=$2;
REGEX="(repository|workflow)_dispatch"
# set test-e2e params
if [ -n "$3" ]; then
e2eParams="--$3"
if [ -n "$2" ]; then
e2eParams="--$2"
else
e2eParams=""
fi
echo "Step1 - Verify if affected libs contains $verifyLib or if deps $deps are affected"
echo "Step1 - Verify if affected libs contains $verifyLib"
AFFECTED_LIB=$(./scripts/github/affected-contains.sh $verifyLib $deps)
AFFECTED_LIB=$(./scripts/github/affected-contains.sh $verifyLib )
if [ ${AFFECTED_LIB} == true ]; then
echo "Step2 - $verifyLib OR deps $deps affected... will execute e2e"
echo "Step2 - $verifyLib affected... will execute e2e"
if [ "${GITHUB_EVENT_NAME}" == "pull_request" ]; then
echo "Calculate affected e2e $BASE_HASH $HEAD_HASH"
@@ -55,6 +54,6 @@ if [ ${AFFECTED_LIB} == true ]; then
fi;
else
echo "Step2 - Lib $verifyLib OR deps $deps NOT affected. No need to run e2e"
echo "Step2 - Lib $verifyLib NOT affected. No need to run e2e"
exit 0
fi

Some files were not shown because too many files have changed in this diff Show More