Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { TranslateModule } from '@ngx-translate/core';

import { environment } from '../../../environments/environment.test';
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
import { HostWindowService } from '../../shared/host-window.service';
import { SelectableListService } from '../../shared/object-list/selectable-list/selectable-list.service';
import { SearchConfigurationService } from '../../shared/search/search-configuration.service';
Expand Down Expand Up @@ -161,6 +162,7 @@ describe('TopLevelCommunityListComponent', () => {
{ provide: LinkHeadService, useValue: linkHeadService },
{ provide: ConfigurationDataService, useValue: configurationDataService },
{ provide: SearchConfigurationService, useValue: new SearchConfigurationServiceStub() },
{ provide: DSpaceObjectDataService, useValue: {} },
],
schemas: [NO_ERRORS_SCHEMA],
}).compileComponents();
Expand Down
301 changes: 221 additions & 80 deletions src/app/shared/rss-feed/rss.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,23 @@ import {
SortOptions,
} from '@dspace/core/cache/models/sort-options.model';
import { ConfigurationDataService } from '@dspace/core/data/configuration-data.service';
import { RemoteData } from '@dspace/core/data/remote-data';
import { GroupDataService } from '@dspace/core/eperson/group-data.service';
import { PaginationService } from '@dspace/core/pagination/pagination.service';
import { PaginationComponentOptions } from '@dspace/core/pagination/pagination-component-options.model';
import { LinkHeadService } from '@dspace/core/services/link-head.service';
import { Collection } from '@dspace/core/shared/collection.model';
import { ConfigurationProperty } from '@dspace/core/shared/configuration-property.model';
import { PaginatedSearchOptions } from '@dspace/core/shared/search/models/paginated-search-options.model';
import { SearchFilter } from '@dspace/core/shared/search/models/search-filter.model';
import { MockActivatedRoute } from '@dspace/core/testing/active-router.mock';
import { PaginationServiceStub } from '@dspace/core/testing/pagination-service.stub';
import { RouterMock } from '@dspace/core/testing/router.mock';
import { SearchConfigurationServiceStub } from '@dspace/core/testing/search-configuration-service.stub';
import { getMockTranslateService } from '@dspace/core/testing/translate.service.mock';
import { createPaginatedList } from '@dspace/core/testing/utils.test';
import {
createSuccessfulRemoteDataObject,
createSuccessfulRemoteDataObject$,
} from '@dspace/core/utilities/remote-data.utils';
import { createSuccessfulRemoteDataObject$ } from '@dspace/core/utilities/remote-data.utils';
import { TranslateService } from '@ngx-translate/core';
import { of } from 'rxjs';

import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
import { Community } from '../../core/shared/community.model';
import { SearchConfigurationService } from '../search/search-configuration.service';
import { RSSComponent } from './rss.component';

Expand All @@ -43,58 +38,97 @@ describe('RssComponent', () => {
let fixture: ComponentFixture<RSSComponent>;
let uuid: string;
let query: string;
let groupDataService: GroupDataService;
let linkHeadService: LinkHeadService;
let configurationDataService: ConfigurationDataService;
let configurationDataService: jasmine.SpyObj<ConfigurationDataService>;
let groupDataService: jasmine.SpyObj<GroupDataService>;
let dspaceObjectService: jasmine.SpyObj<DSpaceObjectDataService>;
let linkHeadService: jasmine.SpyObj<LinkHeadService>;
let paginationService;

beforeEach(waitForAsync(() => {
const mockCollection: Collection = Object.assign(new Collection(), {
id: 'ce41d451-97ed-4a9c-94a1-7de34f16a9f4',
name: 'test-collection',
_links: {
mappedItems: {
href: 'https://rest.api/collections/ce41d451-97ed-4a9c-94a1-7de34f16a9f4/mappedItems',
},
self: {
href: 'https://rest.api/collections/ce41d451-97ed-4a9c-94a1-7de34f16a9f4',
},
const mockCollection: Collection = Object.assign(new Collection(), {
id: 'ce41d451-97ed-4a9c-94a1-7de34f16a9f4',
name: 'test-collection',
_links: {
mappedItems: {
href: 'https://rest.api/collections/ce41d451-97ed-4a9c-94a1-7de34f16a9f4/mappedItems',
},
self: {
href: 'https://rest.api/collections/ce41d451-97ed-4a9c-94a1-7de34f16a9f4',
},
},
});

const mockCommunity: Community = Object.assign(new Community(), {
id: 'da9a4b37-3e8e-402e-9b14-7c5b8a1d4f21',
name: 'test-community',
_links: {
self: {
href: 'https://rest.api/communities/da9a4b37-3e8e-402e-9b14-7c5b8a1d4f21',
},
},
});

/**
* Reconfigure the configurationDataService spy and reinitialise the component.
* @param formats The raw format values to expose via websvc.opensearch.formats
* @param dso Either the collection or community that we are acting like the rss page is loaded on
*/
function setupComponent(formats: string[], scopedDso?: Collection | Community): void {
configurationDataService = TestBed.inject(ConfigurationDataService) as jasmine.SpyObj<ConfigurationDataService>;
(configurationDataService.findByPropertyName as jasmine.Spy).and.callFake((property: string) => {
switch (property) {
case 'websvc.opensearch.enable':
return createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(), {
name: 'websvc.opensearch.enable',
values: ['true'],
}));
case 'websvc.opensearch.formats':
return createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(), {
name: 'websvc.opensearch.formats',
values: formats,
}));
case 'websvc.opensearch.svccontext':
return createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(), {
name: 'websvc.opensearch.svccontext',
values: ['opensearch/search'],
}));
default:
return createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(), {
name: property,
values: [],
}));
}
});
configurationDataService = jasmine.createSpyObj('configurationDataService', {
findByPropertyName: createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(), {
name: 'test',
values: [
'org.dspace.ctask.general.ProfileFormats = test',
],
})),
});
linkHeadService = jasmine.createSpyObj('linkHeadService', {
addTag: '',
});
const mockCollectionRD: RemoteData<Collection> = createSuccessfulRemoteDataObject(mockCollection);
const mockSearchOptions = of(new PaginatedSearchOptions({
pagination: Object.assign(new PaginationComponentOptions(), {
id: 'search-page-configuration',
pageSize: 10,
currentPage: 1,
}),
sort: new SortOptions('dc.title', SortDirection.ASC),
}));
groupDataService = jasmine.createSpyObj('groupsDataService', {
findListByHref: createSuccessfulRemoteDataObject$(createPaginatedList([])),
getGroupRegistryRouterLink: '',
getUUIDFromString: '',
});

groupDataService = TestBed.inject(GroupDataService) as jasmine.SpyObj<GroupDataService>;
groupDataService.findListByHref.and.returnValue(
createSuccessfulRemoteDataObject$(createPaginatedList([])),
);
groupDataService.getGroupRegistryRouterLink.and.returnValue('');

linkHeadService = TestBed.inject(LinkHeadService) as jasmine.SpyObj<LinkHeadService>;
linkHeadService.addTag.calls.reset();
linkHeadService.removeTag.calls.reset();

dspaceObjectService = TestBed.inject(DSpaceObjectDataService) as jasmine.SpyObj<DSpaceObjectDataService>;
if (scopedDso) {
dspaceObjectService.findById.and.returnValue(createSuccessfulRemoteDataObject$(scopedDso));
groupDataService.getUUIDFromString.and.returnValue(scopedDso.id);
}

fixture = TestBed.createComponent(RSSComponent);
comp = fixture.componentInstance;
fixture.detectChanges();
}

beforeEach(waitForAsync(() => {
paginationService = new PaginationServiceStub();
const searchConfigService = {
paginatedSearchOptions: mockSearchOptions,
};

TestBed.configureTestingModule({
providers: [
{ provide: GroupDataService, useValue: groupDataService },
{ provide: LinkHeadService, useValue: linkHeadService },
{ provide: ConfigurationDataService, useValue: configurationDataService },
{ provide: ConfigurationDataService, useValue: jasmine.createSpyObj('ConfigurationDataService', ['findByPropertyName']) },
{ provide: GroupDataService, useValue: jasmine.createSpyObj('GroupDataService', ['findListByHref', 'getGroupRegistryRouterLink', 'getUUIDFromString']) },
{ provide: DSpaceObjectDataService, useValue: jasmine.createSpyObj('DSpaceObjectDataService', ['findById']) },
{ provide: LinkHeadService, useValue: jasmine.createSpyObj('LinkHeadService', ['addTag', 'removeTag']) },
{ provide: SearchConfigurationService, useValue: new SearchConfigurationServiceStub() },
{ provide: PaginationService, useValue: paginationService },
{ provide: Router, useValue: new RouterMock() },
Expand All @@ -109,42 +143,149 @@ describe('RssComponent', () => {
options = new SortOptions('dc.title', SortDirection.DESC);
uuid = '2cfcf65e-0a51-4bcb-8592-b8db7b064790';
query = 'test';
fixture = TestBed.createComponent(RSSComponent);
comp = fixture.componentInstance;
});

it('should formulate the correct url given params in url', () => {
const route = comp.formulateRoute(uuid, 'opensearch/search', options, query);
expect(route).toBe('/opensearch/search?format=atom&scope=2cfcf65e-0a51-4bcb-8592-b8db7b064790&sort=dc.title&sort_direction=DESC&query=test');
});
describe('formulateRoute', () => {
beforeEach(() => {
fixture = TestBed.createComponent(RSSComponent);
comp = fixture.componentInstance;
});

it('should skip uuid if its null', () => {
const route = comp.formulateRoute(null, 'opensearch/search', options, query);
expect(route).toBe('/opensearch/search?format=atom&sort=dc.title&sort_direction=DESC&query=test');
});
it('should formulate the correct url given params in url', () => {
const route = comp.formulateRoute(uuid, 'opensearch/search', 'atom', options, query);
expect(route).toBe('/opensearch/search?format=atom&scope=2cfcf65e-0a51-4bcb-8592-b8db7b064790&sort=dc.title&sort_direction=DESC&query=test');
});

it('should skip uuid if its null', () => {
const route = comp.formulateRoute(null, 'opensearch/search', 'atom', options, query);
expect(route).toBe('/opensearch/search?format=atom&sort=dc.title&sort_direction=DESC&query=test');
});

it('should default to query * if none provided', () => {
const route = comp.formulateRoute(null, 'opensearch/search', 'atom', options, null);
expect(route).toBe('/opensearch/search?format=atom&sort=dc.title&sort_direction=DESC&query=*');
});

it('should include filters in opensearch url if provided', () => {
const filters = [
new SearchFilter('f.test', ['value', 'another value'], 'contains'),
new SearchFilter('f.range', ['[1987 TO 1988]'], 'equals'),
];
const route = comp.formulateRoute(uuid, 'opensearch/search', 'atom', options, query, filters);
expect(route).toBe('/opensearch/search?format=atom&scope=2cfcf65e-0a51-4bcb-8592-b8db7b064790&sort=dc.title&sort_direction=DESC&query=test&f.test=value,contains&f.test=another%20value,contains&f.range=%5B1987%20TO%201988%5D,equals');
});

it('should include configuration in opensearch url if provided', () => {
const route = comp.formulateRoute(uuid, 'opensearch/search', 'atom', options, query, null, 'adminConfiguration');
expect(route).toBe('/opensearch/search?format=atom&scope=2cfcf65e-0a51-4bcb-8592-b8db7b064790&sort=dc.title&sort_direction=DESC&query=test&configuration=adminConfiguration');
});

it('should default to query * if none provided', () => {
const route = comp.formulateRoute(null, 'opensearch/search', options, null);
expect(route).toBe('/opensearch/search?format=atom&sort=dc.title&sort_direction=DESC&query=*');
it('should include rpp in opensearch url if provided', () => {
const route = comp.formulateRoute(uuid, 'opensearch/search', 'atom', options, query, null, null, 50);
expect(route).toBe('/opensearch/search?format=atom&scope=2cfcf65e-0a51-4bcb-8592-b8db7b064790&sort=dc.title&sort_direction=DESC&query=test&rpp=50');
});
});

it('should include filters in opensearch url if provided', () => {
const filters = [
new SearchFilter('f.test', ['value','another value'], 'contains'), // should be split into two arguments, spaces should be URI-encoded
new SearchFilter('f.range', ['[1987 TO 1988]'], 'equals'), // value should be URI-encoded, ',equals' should not
];
const route = comp.formulateRoute(uuid, 'opensearch/search', options, query, filters);
expect(route).toBe('/opensearch/search?format=atom&scope=2cfcf65e-0a51-4bcb-8592-b8db7b064790&sort=dc.title&sort_direction=DESC&query=test&f.test=value,contains&f.test=another%20value,contains&f.range=%5B1987%20TO%201988%5D,equals');
describe('when formats are configured as html,atom,rss', () => {
beforeEach(() => {
setupComponent(['html', 'atom', 'rss']);
});

it('should set formats$ to only the recognised formats, dropping html', () => {
expect(comp.formats$.getValue()).toEqual(['atom', 'rss']);
});

it('should set route$ to the atom feed (first recognised format)', () => {
expect(comp.route$.getValue()).toContain('format=atom');
});

it('should add a rel="search" link pointing to the atom feed', () => {
const searchTag = (linkHeadService.addTag as jasmine.Spy).calls.all()
.map(c => c.args[0])
.find(tag => tag.rel === 'search');
expect(searchTag).toBeTruthy();
expect(searchTag.type).toBe('application/atom+xml');
});

it('should add a rel="alternate" link for atom', () => {
const alternateTags = (linkHeadService.addTag as jasmine.Spy).calls.all()
.map(c => c.args[0])
.filter(tag => tag.rel === 'alternate');
expect(alternateTags.some(t => t.type === 'application/atom+xml')).toBeTrue();
});

it('should add a rel="alternate" link for rss', () => {
const alternateTags = (linkHeadService.addTag as jasmine.Spy).calls.all()
.map(c => c.args[0])
.filter(tag => tag.rel === 'alternate');
expect(alternateTags.some(t => t.type === 'application/rss+xml')).toBeTrue();
});
});

it('should include configuration in opensearch url if provided', () => {
const route = comp.formulateRoute(uuid, 'opensearch/search', options, query, null, 'adminConfiguration');
expect(route).toBe('/opensearch/search?format=atom&scope=2cfcf65e-0a51-4bcb-8592-b8db7b064790&sort=dc.title&sort_direction=DESC&query=test&configuration=adminConfiguration');
describe('when formats are configured as html,rss', () => {
beforeEach(() => {
setupComponent(['html', 'rss']);
});

it('should set formats$ to only the recognised formats, dropping html', () => {
expect(comp.formats$.getValue()).toEqual(['rss']);
});

it('should set route$ to the rss feed (first recognised format)', () => {
expect(comp.route$.getValue()).toContain('format=rss');
});

it('should add a rel="search" link pointing to the rss feed', () => {
const searchTag = (linkHeadService.addTag as jasmine.Spy).calls.all()
.map(c => c.args[0])
.find(tag => tag.rel === 'search');
expect(searchTag).toBeTruthy();
expect(searchTag.type).toBe('application/rss+xml');
});

it('should add a rel="alternate" link only for rss', () => {
const alternateTags = (linkHeadService.addTag as jasmine.Spy).calls.all()
.map(c => c.args[0])
.filter(tag => tag.rel === 'alternate');
expect(alternateTags.length).toBe(1);
expect(alternateTags[0].type).toBe('application/rss+xml');
});

it('should not add a rel="alternate" link for atom', () => {
const alternateTags = (linkHeadService.addTag as jasmine.Spy).calls.all()
.map(c => c.args[0])
.filter(tag => tag.rel === 'alternate');
expect(alternateTags.some(t => t.type === 'application/atom+xml')).toBeFalse();
});
});

it('should include rpp in opensearch url if provided', () => {
const route = comp.formulateRoute(uuid, 'opensearch/search', options, query, null, null, 50);
expect(route).toBe('/opensearch/search?format=atom&scope=2cfcf65e-0a51-4bcb-8592-b8db7b064790&sort=dc.title&sort_direction=DESC&query=test&rpp=50');
describe('when scoped to a collection', () => {
beforeEach(() => {
setupComponent(['html', 'atom', 'rss'], mockCollection);
});

it('should include the collection name in the rel="alternate" link titles', () => {
const alternateTags = (linkHeadService.addTag as jasmine.Spy).calls.all()
.map(c => c.args[0])
.filter(tag => tag.rel === 'alternate');
for (const tag of alternateTags) {
expect(tag.title).toContain(mockCollection.name);
}
});

describe('when scoped to a community', () => {
beforeEach(() => {
setupComponent(['html', 'atom', 'rss'], mockCommunity);
});

it('should include the community name in the rel="alternate" link titles', () => {
const alternateTags = (linkHeadService.addTag as jasmine.Spy).calls.all()
.map(c => c.args[0])
.filter(tag => tag.rel === 'alternate');
for (const tag of alternateTags) {
expect(tag.title).toContain(mockCommunity.name);
}
});
});
});
});

Loading
Loading