mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2025-05-12 17:04:46 +00:00
fix unit tests
This commit is contained in:
parent
1bf4f26df8
commit
3a5bf73c79
@ -12,6 +12,19 @@ module.exports = function (config) {
|
|||||||
require('karma-coverage-istanbul-reporter'),
|
require('karma-coverage-istanbul-reporter'),
|
||||||
require('@angular/cli/plugins/karma')
|
require('@angular/cli/plugins/karma')
|
||||||
],
|
],
|
||||||
|
files: [
|
||||||
|
{ pattern: './node_modules/hammerjs/hammer.js', watched: false },
|
||||||
|
{ pattern: './node_modules/@angular/material/prebuilt-themes/indigo-pink.css', watched: false },
|
||||||
|
{ pattern: './node_modules/ng2-alfresco-*/bundles/assets/ng2-alfresco-*/i18n/en.json', watched: false, served: true, included: false },
|
||||||
|
],
|
||||||
|
proxies: {
|
||||||
|
'/assets/ng2-alfresco-core/i18n/en.json': '/base/node_modules/ng2-alfresco-core/bundles/assets/ng2-alfresco-core/i18n/en.json',
|
||||||
|
'/assets/ng2-alfresco-datatable/i18n/en.json': '/base/node_modules/ng2-alfresco-datatable/bundles/assets/ng2-alfresco-datatable/i18n/en.json',
|
||||||
|
'/assets/ng2-alfresco-documentlist/i18n/en.json': '/base/node_modules/ng2-alfresco-documentlist/bundles/assets/ng2-alfresco-documentlist/i18n/en.json',
|
||||||
|
'/assets/ng2-alfresco-login/i18n/en.json': '/base/node_modules/ng2-alfresco-login/bundles/assets/ng2-alfresco-login/i18n/en.json',
|
||||||
|
'/assets/ng2-alfresco-upload/i18n/en.json': '/base/node_modules/ng2-alfresco-upload/bundles/assets/ng2-alfresco-upload/i18n/en.json',
|
||||||
|
'/assets/ng2-alfresco-search/i18n/en.json': '/base/node_modules/ng2-alfresco-search/bundles/assets/ng2-alfresco-search/i18n/en.json'
|
||||||
|
},
|
||||||
client:{
|
client:{
|
||||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||||
},
|
},
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
import { TestBed, async } from '@angular/core/testing';
|
|
||||||
import { AppComponent } from './app.component';
|
|
||||||
describe('AppComponent', () => {
|
|
||||||
beforeEach(async(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
declarations: [
|
|
||||||
AppComponent
|
|
||||||
],
|
|
||||||
}).compileComponents();
|
|
||||||
}));
|
|
||||||
it('should create the app', async(() => {
|
|
||||||
const fixture = TestBed.createComponent(AppComponent);
|
|
||||||
const app = fixture.debugElement.componentInstance;
|
|
||||||
expect(app).toBeTruthy();
|
|
||||||
}));
|
|
||||||
it(`should have as title 'app'`, async(() => {
|
|
||||||
const fixture = TestBed.createComponent(AppComponent);
|
|
||||||
const app = fixture.debugElement.componentInstance;
|
|
||||||
expect(app.title).toEqual('app');
|
|
||||||
}));
|
|
||||||
it('should render title in a h1 tag', async(() => {
|
|
||||||
const fixture = TestBed.createComponent(AppComponent);
|
|
||||||
fixture.detectChanges();
|
|
||||||
const compiled = fixture.debugElement.nativeElement;
|
|
||||||
expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
|
|
||||||
}));
|
|
||||||
});
|
|
@ -25,7 +25,7 @@ describe('BrowsingFilesService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('subscribs to event', () => {
|
it('subscribs to event', () => {
|
||||||
const value = 'test-value';
|
const value: any = 'test-value';
|
||||||
|
|
||||||
service.onChangeParent.subscribe((result) => {
|
service.onChangeParent.subscribe((result) => {
|
||||||
expect(result).toBe(value);
|
expect(result).toBe(value);
|
||||||
|
@ -33,10 +33,10 @@ describe('NodeActionsService', () => {
|
|||||||
let apiService: AlfrescoApiService;
|
let apiService: AlfrescoApiService;
|
||||||
let nodesApiService: NodesApiService;
|
let nodesApiService: NodesApiService;
|
||||||
let nodesApi;
|
let nodesApi;
|
||||||
let spyOnSuccess = jasmine.createSpy('spyOnSuccess');
|
const spyOnSuccess = jasmine.createSpy('spyOnSuccess');
|
||||||
let spyOnError = jasmine.createSpy('spyOnError');
|
const spyOnError = jasmine.createSpy('spyOnError');
|
||||||
|
|
||||||
let helper = {
|
const helper = {
|
||||||
fakeCopyNode: (isForbidden: boolean = false, nameExistingOnDestination?: string) => {
|
fakeCopyNode: (isForbidden: boolean = false, nameExistingOnDestination?: string) => {
|
||||||
return (entryId, options) => {
|
return (entryId, options) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@ -98,7 +98,7 @@ describe('NodeActionsService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should throw error if \'contentEntities\' required parameter is missing', async(() => {
|
it('should throw error if \'contentEntities\' required parameter is missing', async(() => {
|
||||||
let contentEntities;
|
const contentEntities = undefined;
|
||||||
const doCopyBatchOperation = service.copyNodes(contentEntities).asObservable();
|
const doCopyBatchOperation = service.copyNodes(contentEntities).asObservable();
|
||||||
|
|
||||||
doCopyBatchOperation.toPromise().then(
|
doCopyBatchOperation.toPromise().then(
|
||||||
@ -243,7 +243,7 @@ describe('NodeActionsService', () => {
|
|||||||
expect(spyOnBatchOperation).toHaveBeenCalledWith('copy', [fileToCopy, folderToCopy], undefined);
|
expect(spyOnBatchOperation).toHaveBeenCalledWith('copy', [fileToCopy, folderToCopy], undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should use the ContentNodeSelectorComponentData object with custom rowFilter & imageResolver & title, when opening the destination picker', () => {
|
it('should use the custom data object with custom rowFilter & imageResolver & title with destination picker', () => {
|
||||||
const spyOnBatchOperation = spyOn(service, 'doBatchOperation').and.callThrough();
|
const spyOnBatchOperation = spyOn(service, 'doBatchOperation').and.callThrough();
|
||||||
const spyOnDestinationPicker = spyOn(service, 'getContentNodeSelection').and.callThrough();
|
const spyOnDestinationPicker = spyOn(service, 'getContentNodeSelection').and.callThrough();
|
||||||
spyOn(service, 'getFirstParentId').and.returnValue('parent-id');
|
spyOn(service, 'getFirstParentId').and.returnValue('parent-id');
|
||||||
@ -315,8 +315,8 @@ describe('NodeActionsService', () => {
|
|||||||
it('should copy one folder node to destination', () => {
|
it('should copy one folder node to destination', () => {
|
||||||
spyOn(nodesApi, 'copyNode').and.callFake(helper.fakeCopyNode());
|
spyOn(nodesApi, 'copyNode').and.callFake(helper.fakeCopyNode());
|
||||||
|
|
||||||
let folderToCopy = new TestNode();
|
const folderToCopy = new TestNode();
|
||||||
let folderDestination = new TestNode(folderDestinationId);
|
const folderDestination = new TestNode(folderDestinationId);
|
||||||
service.copyNodeAction(folderToCopy.entry, folderDestination.entry.id);
|
service.copyNodeAction(folderToCopy.entry, folderDestination.entry.id);
|
||||||
|
|
||||||
expect(nodesApi.copyNode).toHaveBeenCalledWith(
|
expect(nodesApi.copyNode).toHaveBeenCalledWith(
|
||||||
@ -328,8 +328,8 @@ describe('NodeActionsService', () => {
|
|||||||
it('should copy one file node to destination', () => {
|
it('should copy one file node to destination', () => {
|
||||||
spyOn(nodesApi, 'copyNode').and.callFake(helper.fakeCopyNode());
|
spyOn(nodesApi, 'copyNode').and.callFake(helper.fakeCopyNode());
|
||||||
|
|
||||||
let fileToCopy = new TestNode(fileId, isFile, 'file-name');
|
const fileToCopy = new TestNode(fileId, isFile, 'file-name');
|
||||||
let folderDestination = new TestNode(folderDestinationId);
|
const folderDestination = new TestNode(folderDestinationId);
|
||||||
service.copyNodeAction(fileToCopy.entry, folderDestination.entry.id);
|
service.copyNodeAction(fileToCopy.entry, folderDestination.entry.id);
|
||||||
|
|
||||||
expect(nodesApi.copyNode).toHaveBeenCalledWith(
|
expect(nodesApi.copyNode).toHaveBeenCalledWith(
|
||||||
@ -341,8 +341,8 @@ describe('NodeActionsService', () => {
|
|||||||
it('should fail to copy folder node if action is forbidden', async(() => {
|
it('should fail to copy folder node if action is forbidden', async(() => {
|
||||||
spyOn(nodesApi, 'copyNode').and.callFake(helper.fakeCopyNode(actionIsForbidden));
|
spyOn(nodesApi, 'copyNode').and.callFake(helper.fakeCopyNode(actionIsForbidden));
|
||||||
|
|
||||||
let folderToCopy = new TestNode();
|
const folderToCopy = new TestNode();
|
||||||
let folderDestination = new TestNode(folderDestinationId);
|
const folderDestination = new TestNode(folderDestinationId);
|
||||||
|
|
||||||
const spyContentAction = spyOn(service, 'copyContentAction').and.callThrough();
|
const spyContentAction = spyOn(service, 'copyContentAction').and.callThrough();
|
||||||
const spyFolderAction = spyOn(service, 'copyFolderAction').and.callThrough();
|
const spyFolderAction = spyOn(service, 'copyFolderAction').and.callThrough();
|
||||||
@ -375,8 +375,8 @@ describe('NodeActionsService', () => {
|
|||||||
const spyContentAction = spyOn(service, 'copyContentAction').and.callThrough();
|
const spyContentAction = spyOn(service, 'copyContentAction').and.callThrough();
|
||||||
const spyFolderAction = spyOn(service, 'copyFolderAction').and.callThrough();
|
const spyFolderAction = spyOn(service, 'copyFolderAction').and.callThrough();
|
||||||
|
|
||||||
let fileToCopy = new TestNode(fileId, isFile, 'test-name');
|
const fileToCopy = new TestNode(fileId, isFile, 'test-name');
|
||||||
let folderDestination = new TestNode(folderDestinationId);
|
const folderDestination = new TestNode(folderDestinationId);
|
||||||
const copyObservable = service.copyNodeAction(fileToCopy.entry, folderDestination.entry.id);
|
const copyObservable = service.copyNodeAction(fileToCopy.entry, folderDestination.entry.id);
|
||||||
|
|
||||||
spyOnSuccess.calls.reset();
|
spyOnSuccess.calls.reset();
|
||||||
@ -409,8 +409,8 @@ describe('NodeActionsService', () => {
|
|||||||
|
|
||||||
const spyContentAction = spyOn(service, 'copyContentAction').and.callThrough();
|
const spyContentAction = spyOn(service, 'copyContentAction').and.callThrough();
|
||||||
|
|
||||||
let fileToCopy = new TestNode(fileId, isFile, 'file-name');
|
const fileToCopy = new TestNode(fileId, isFile, 'file-name');
|
||||||
let folderDestination = new TestNode(folderDestinationId);
|
const folderDestination = new TestNode(folderDestinationId);
|
||||||
const copyObservable = service.copyNodeAction(fileToCopy.entry, folderDestination.entry.id);
|
const copyObservable = service.copyNodeAction(fileToCopy.entry, folderDestination.entry.id);
|
||||||
|
|
||||||
spyOnSuccess.calls.reset();
|
spyOnSuccess.calls.reset();
|
||||||
@ -607,7 +607,9 @@ describe('NodeActionsService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should allow to select destination for nodes that have permission to be moved', () => {
|
it('should allow to select destination for nodes that have permission to be moved', () => {
|
||||||
const spyOnDestinationPicker = spyOn(service, 'getContentNodeSelection').and.returnValue(Observable.of([destinationFolder.entry]));
|
const spyOnDestinationPicker =
|
||||||
|
spyOn(service, 'getContentNodeSelection')
|
||||||
|
.and.returnValue(Observable.of([destinationFolder.entry]));
|
||||||
spyOn(service, 'moveContentAction').and.returnValue(Observable.of({}));
|
spyOn(service, 'moveContentAction').and.returnValue(Observable.of({}));
|
||||||
spyOn(service, 'moveFolderAction').and.returnValue(Observable.of({}));
|
spyOn(service, 'moveFolderAction').and.returnValue(Observable.of({}));
|
||||||
|
|
||||||
@ -620,7 +622,9 @@ describe('NodeActionsService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not allow to select destination for nodes that do not have permission to be moved', () => {
|
it('should not allow to select destination for nodes that do not have permission to be moved', () => {
|
||||||
const spyOnDestinationPicker = spyOn(service, 'getContentNodeSelection').and.returnValue(Observable.of([destinationFolder.entry]));
|
const spyOnDestinationPicker =
|
||||||
|
spyOn(service, 'getContentNodeSelection')
|
||||||
|
.and.returnValue(Observable.of([destinationFolder.entry]));
|
||||||
|
|
||||||
fileToMove.entry['allowableOperations'] = [];
|
fileToMove.entry['allowableOperations'] = [];
|
||||||
folderToMove.entry['allowableOperations'] = [];
|
folderToMove.entry['allowableOperations'] = [];
|
||||||
@ -631,7 +635,9 @@ describe('NodeActionsService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should call the documentListService moveNode directly for moving a file that has permission to be moved', () => {
|
it('should call the documentListService moveNode directly for moving a file that has permission to be moved', () => {
|
||||||
const spyOnDestinationPicker = spyOn(service, 'getContentNodeSelection').and.returnValue(Observable.of([destinationFolder.entry]));
|
const spyOnDestinationPicker =
|
||||||
|
spyOn(service, 'getContentNodeSelection')
|
||||||
|
.and.returnValue(Observable.of([destinationFolder.entry]));
|
||||||
fileToMove.entry['allowableOperations'] = [permissionToMove];
|
fileToMove.entry['allowableOperations'] = [permissionToMove];
|
||||||
spyOnDocumentListServiceAction = spyOn(documentListService, 'moveNode').and.returnValue(Observable.of([fileToMove]));
|
spyOnDocumentListServiceAction = spyOn(documentListService, 'moveNode').and.returnValue(Observable.of([fileToMove]));
|
||||||
spyOn(service, 'moveNodeAction');
|
spyOn(service, 'moveNodeAction');
|
||||||
@ -873,7 +879,7 @@ describe('NodeActionsService', () => {
|
|||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should take extra delete action, if all children nodes were successfully moved and folder is still on location', async(() => {
|
it('should take extra delete action, if children successfully moved and folder is still on location', async(() => {
|
||||||
const movedChildrenNodes = [ fileToMove, folderToMove ];
|
const movedChildrenNodes = [ fileToMove, folderToMove ];
|
||||||
spyOn(service, 'moveFolderAction').and.returnValue(Observable.of(movedChildrenNodes));
|
spyOn(service, 'moveFolderAction').and.returnValue(Observable.of(movedChildrenNodes));
|
||||||
spyOn(service, 'processResponse').and.returnValue({
|
spyOn(service, 'processResponse').and.returnValue({
|
||||||
@ -942,7 +948,7 @@ describe('NodeActionsService', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
childNode = new TestNode(fileId, isFile, 'child-name');
|
childNode = new TestNode(fileId, isFile, 'child-name');
|
||||||
let parentNode = new TestNode();
|
const parentNode = new TestNode();
|
||||||
|
|
||||||
notChildNode = new TestNode('not-child-id', !isFile, 'not-child-name');
|
notChildNode = new TestNode('not-child-id', !isFile, 'not-child-name');
|
||||||
testFamilyNodes = [
|
testFamilyNodes = [
|
||||||
@ -991,7 +997,7 @@ describe('NodeActionsService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('getNewNameFrom', () => {
|
describe('getNewNameFrom', () => {
|
||||||
let testData = [
|
const testData = [
|
||||||
{
|
{
|
||||||
name: 'noExtension',
|
name: 'noExtension',
|
||||||
baseName: 'noExtension',
|
baseName: 'noExtension',
|
||||||
@ -1043,7 +1049,7 @@ describe('NodeActionsService', () => {
|
|||||||
const testNode1 = new TestNode('node1-id', isFile, 'node1-name');
|
const testNode1 = new TestNode('node1-id', isFile, 'node1-name');
|
||||||
const testNode2 = new TestNode('node2-id', !isFile, 'node2-name');
|
const testNode2 = new TestNode('node2-id', !isFile, 'node2-name');
|
||||||
|
|
||||||
let testData = [
|
const testData = [
|
||||||
{
|
{
|
||||||
nDimArray: [ testNode1 ],
|
nDimArray: [ testNode1 ],
|
||||||
expected: [ testNode1 ]
|
expected: [ testNode1 ]
|
||||||
@ -1076,7 +1082,7 @@ describe('NodeActionsService', () => {
|
|||||||
const parentID = 'patent-1-id';
|
const parentID = 'patent-1-id';
|
||||||
testNode1.entry['parentId'] = parentID;
|
testNode1.entry['parentId'] = parentID;
|
||||||
|
|
||||||
let testData = [
|
const testData = [
|
||||||
{
|
{
|
||||||
data: [ testNode1 ],
|
data: [ testNode1 ],
|
||||||
expected: {
|
expected: {
|
||||||
|
@ -21,13 +21,16 @@ import { Observable, Subject } from 'rxjs/Rx';
|
|||||||
|
|
||||||
import { AlfrescoApiService, AlfrescoContentService, NodesApiService } from 'ng2-alfresco-core';
|
import { AlfrescoApiService, AlfrescoContentService, NodesApiService } from 'ng2-alfresco-core';
|
||||||
import { DataColumn } from 'ng2-alfresco-datatable';
|
import { DataColumn } from 'ng2-alfresco-datatable';
|
||||||
import { DocumentListService, ContentNodeSelectorComponent, ContentNodeSelectorComponentData, ShareDataRow } from 'ng2-alfresco-documentlist';
|
import {
|
||||||
|
DocumentListService, ContentNodeSelectorComponent,
|
||||||
|
ContentNodeSelectorComponentData, ShareDataRow
|
||||||
|
} from 'ng2-alfresco-documentlist';
|
||||||
import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api';
|
import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class NodeActionsService {
|
export class NodeActionsService {
|
||||||
static SNACK_MESSAGE_DURATION_WITH_UNDO: number = 10000;
|
static SNACK_MESSAGE_DURATION_WITH_UNDO = 10000;
|
||||||
static SNACK_MESSAGE_DURATION: number = 3000;
|
static SNACK_MESSAGE_DURATION = 3000;
|
||||||
|
|
||||||
contentCopied: Subject<MinimalNodeEntity[]> = new Subject<MinimalNodeEntity[]>();
|
contentCopied: Subject<MinimalNodeEntity[]> = new Subject<MinimalNodeEntity[]>();
|
||||||
contentMoved: Subject<any> = new Subject<any>();
|
contentMoved: Subject<any> = new Subject<any>();
|
||||||
@ -66,7 +69,7 @@ export class NodeActionsService {
|
|||||||
* @param contentEntities the contentEntities which have to have the action performed on
|
* @param contentEntities the contentEntities which have to have the action performed on
|
||||||
* @param permission permission which is needed to apply the action
|
* @param permission permission which is needed to apply the action
|
||||||
*/
|
*/
|
||||||
private doBatchOperation(action: string, contentEntities: any[], permission?: string): Subject<string> {
|
doBatchOperation(action: string, contentEntities: any[], permission?: string): Subject<string> {
|
||||||
const observable: Subject<string> = new Subject<string>();
|
const observable: Subject<string> = new Subject<string>();
|
||||||
|
|
||||||
if (!this.isEntryEntitiesArray(contentEntities)) {
|
if (!this.isEntryEntitiesArray(contentEntities)) {
|
||||||
@ -399,7 +402,7 @@ export class NodeActionsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getChildByName(parentId, name) {
|
getChildByName(parentId, name) {
|
||||||
let matchedNodes: Subject<any> = new Subject<any>();
|
const matchedNodes: Subject<any> = new Subject<any>();
|
||||||
|
|
||||||
this.getNodeChildren(parentId).subscribe(
|
this.getNodeChildren(parentId).subscribe(
|
||||||
(childrenNodes) => {
|
(childrenNodes) => {
|
||||||
@ -443,7 +446,7 @@ export class NodeActionsService {
|
|||||||
const extensionMatch = name.match(/\.[^/.]+$/);
|
const extensionMatch = name.match(/\.[^/.]+$/);
|
||||||
|
|
||||||
// remove extension in case there is one
|
// remove extension in case there is one
|
||||||
let fileExtension = extensionMatch ? extensionMatch[0] : '';
|
const fileExtension = extensionMatch ? extensionMatch[0] : '';
|
||||||
let extensionFree = extensionMatch ? name.slice(0, extensionMatch.index) : name;
|
let extensionFree = extensionMatch ? name.slice(0, extensionMatch.index) : name;
|
||||||
|
|
||||||
let prefixNumber = 1;
|
let prefixNumber = 1;
|
||||||
@ -503,8 +506,8 @@ export class NodeActionsService {
|
|||||||
return nDimArray;
|
return nDimArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
let nodeQueue = nDimArray.slice(0);
|
const nodeQueue = nDimArray.slice(0);
|
||||||
let resultingArray = [];
|
const resultingArray = [];
|
||||||
|
|
||||||
do {
|
do {
|
||||||
nodeQueue.forEach(
|
nodeQueue.forEach(
|
||||||
@ -544,7 +547,9 @@ export class NodeActionsService {
|
|||||||
const folderMoveResponseData = this.flatten(next);
|
const folderMoveResponseData = this.flatten(next);
|
||||||
const foundError = folderMoveResponseData.find(node => node instanceof Error);
|
const foundError = folderMoveResponseData.find(node => node instanceof Error);
|
||||||
// data might contain also items of form: { itemMoved, initialParentId }
|
// data might contain also items of form: { itemMoved, initialParentId }
|
||||||
const foundEntry = folderMoveResponseData.find(node => (node.itemMoved && node.itemMoved.entry) || (node && node.entry));
|
const foundEntry = folderMoveResponseData.find(
|
||||||
|
node => (node.itemMoved && node.itemMoved.entry) || (node && node.entry)
|
||||||
|
);
|
||||||
|
|
||||||
if (!foundError) {
|
if (!foundError) {
|
||||||
// consider success if NONE of the items from the folder move response is an error
|
// consider success if NONE of the items from the folder move response is an error
|
||||||
|
@ -53,7 +53,7 @@ describe('SidenavComponent', () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('updates node on change', () => {
|
it('updates node on change', () => {
|
||||||
const node = { entry: { id: 'someNodeId' } };
|
const node: any = { entry: { id: 'someNodeId' } };
|
||||||
|
|
||||||
browsingService.onChangeParent.next(<any>node);
|
browsingService.onChangeParent.next(<any>node);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user