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 @@ -21,6 +21,12 @@ describe('AuthorizedCollectionSelectorComponent', () => {

let notificationsService: NotificationsService;

function createCollection(id: string, name: string): Collection {
return Object.assign(new Collection(), { id, name });
}

const collectionTest = createCollection('col-test', 'test');

Comment on lines +24 to +29
beforeEach(waitForAsync(() => {
collection = Object.assign(new Collection(), {
id: 'authorized-collection'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ export class AuthorizedCollectionSelectorComponent extends DSOSelectorComponent
return searchListService$.pipe(
getFirstCompletedRemoteData(),
map((rd) => Object.assign(new RemoteData(null, null, null, null), rd, {
payload: hasValue(rd.payload) ? buildPaginatedList(rd.payload.pageInfo, rd.payload.page.map((col) => Object.assign(new CollectionSearchResult(), { indexableObject: col }))) : null,
payload: hasValue(rd.payload) ? buildPaginatedList(rd.payload.pageInfo,
rd.payload.page.map((col) => Object.assign(new CollectionSearchResult(), { indexableObject: col }))) : null,
}))
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { hasValue } from '../../empty.util';
import { createPaginatedList } from '../../testing/utils.test';
import { NotificationsService } from '../../notifications/notifications.service';
import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model';
import { SearchFilter } from '../../search/models/search-filter.model';

describe('DSOSelectorComponent', () => {
let component: DSOSelectorComponent;
Expand Down Expand Up @@ -158,6 +159,93 @@ describe('DSOSelectorComponent', () => {
});
});

<<<<<<< Updated upstream
=======
describe('query processing', () => {
beforeEach(() => {
spyOn(searchService, 'search').and.callThrough();
});

describe('for COMMUNITY/COLLECTION types', () => {
beforeEach(() => {
component.types = [DSpaceObjectType.COMMUNITY];
});

it('should use a title startsWith filter for community/collection searches', () => {
component.search('test query', 1);

expect(searchService.search).toHaveBeenCalledWith(
jasmine.objectContaining({
query: '',
filters: [jasmine.objectContaining({
key: 'f.dc.title',
values: ['test query'],
operator: 'startsWith'
})]
}),
null,
true
);
});

it('should pass through internal resource ID queries unchanged', () => {
const resourceIdQuery = component.getCurrentDSOQuery();
component.search(resourceIdQuery, 1);

expect(searchService.search).toHaveBeenCalledWith(
jasmine.objectContaining({
query: resourceIdQuery
}),
null,
true
);
});
});

describe('for ITEM types', () => {
beforeEach(() => {
component.types = [DSpaceObjectType.ITEM];
});

it('should pass through queries unchanged', () => {
component.search('test query', 1);

expect(searchService.search).toHaveBeenCalledWith(
jasmine.objectContaining({
query: 'test query'
}),
null,
true
);
});
});

describe('edge cases', () => {
beforeEach(() => {
component.types = [DSpaceObjectType.COMMUNITY];
});

it('should treat whitespace-only query as empty and apply default sort', () => {
component.sort = new SortOptions('dc.title', SortDirection.ASC);
component.search(' ', 1);

expect(searchService.search).toHaveBeenCalledWith(
jasmine.objectContaining({
query: '',
filters: undefined,
sort: jasmine.objectContaining({
field: 'dc.title',
direction: SortDirection.ASC,
}),
}),
null,
true
);
});
});
});

>>>>>>> Stashed changes
Comment on lines +162 to +248
describe('when search returns an error', () => {
beforeEach(() => {
spyOn(searchService, 'search').and.returnValue(createFailedRemoteDataObject$());
Expand Down
32 changes: 28 additions & 4 deletions src/app/shared/dso-selector/dso-selector/dso-selector.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { NotificationType } from '../../notifications/models/notification-type';
import {
LISTABLE_NOTIFICATION_OBJECT
} from '../../object-list/listable-notification-object/listable-notification-object.resource-type';
import { SearchFilter } from '../../search/models/search-filter.model';

@Component({
selector: 'ds-dso-selector',
Expand Down Expand Up @@ -227,16 +228,39 @@ export class DSOSelectorComponent implements OnInit, OnDestroy {
* @param useCache Whether or not to use the cache
*/
search(query: string, page: number, useCache: boolean = true): Observable<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>> {
// default sort is only used when there is not query
let efectiveSort = query ? null : this.sort;
const rawQuery = query ?? '';
const trimmedQuery = rawQuery.trim();
const hasQuery = isNotEmpty(trimmedQuery);

// default sort is only used when there is no query
let effectiveSort = hasQuery ? null : this.sort;

// For community/collection searches with a query, use a title startsWith filter
// so the backend handles prefix matching — Angular does not need to know about Solr syntax.
const filters: SearchFilter[] = [];
let processedQuery = trimmedQuery;
if (hasQuery) {
// Bypass filter-based search for internal field queries (e.g. search.resourceid:<uuid>)
const isInternalFieldQuery = /^\w[\w.]*:/.test(trimmedQuery);
if (!isInternalFieldQuery
&& (this.types.includes(DSpaceObjectType.COMMUNITY) || this.types.includes(DSpaceObjectType.COLLECTION))) {
// Use f.dc.title so the backend maps to dc.title_sort via startsWith operator.
// Communities and collections explicitly index dc.title_sort, enabling reliable
// case-insensitive prefix matching without requiring a separate title_sort alias.
filters.push(new SearchFilter('f.dc.title', [trimmedQuery], 'startsWith'));
processedQuery = '';
}
}

return this.searchService.search(
new PaginatedSearchOptions({
query: query,
query: processedQuery,
dsoTypes: this.types,
filters: filters.length > 0 ? filters : undefined,
pagination: Object.assign({}, this.defaultPagination, {
currentPage: page
}),
sort: efectiveSort
sort: effectiveSort
}),
null,
useCache,
Expand Down
Loading