Skip to content
Merged
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
21 changes: 13 additions & 8 deletions cypress/e2e/browse-by-subject.cy.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import { testA11y } from 'cypress/support/utils';

describe('Browse By Subject', () => {
it('should pass accessibility tests', () => {
cy.visit('/browse/subject');
describe('Browse By Subject - Removed from FDA Theme', () => {
it('should only show allowed browse types (title, author, dateissued)', () => {
// Navigate to community list
cy.visit('/community-list');

// Wait for <ds-browse-by-metadata-page> to be visible
cy.get('ds-browse-by-metadata').should('be.visible');
// Click on first community
cy.get('ds-community-list a').first().click();

// Analyze <ds-browse-by-metadata-page> for accessibility
testA11y('ds-browse-by-metadata');
// Check that only allowed browse tabs exist
cy.get('a[href*="/browse/title"]').should('exist');
cy.get('a[href*="/browse/author"]').should('exist');
cy.get('a[href*="/browse/dateissued"]').should('exist');

// Verify subject browse tab doesn't exist
cy.get('a[href*="/browse/subject"]').should('not.exist');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<h2 class="comcol-browse-label">{{'browse.comcol.head' | translate}}</h2>
<nav *ngIf="(allOptions$ | async) as allOptions" class="comcol-browse mb-4" aria-label="Browse Community or Collection">
<div class="d-none d-sm-block">

<div class="list-group list-group-horizontal" role="tablist">
<a *ngFor="let option of allOptions"
[attr.aria-current]="(currentOption$ | async)?.id === option.id"
class="list-group-item"
role="tab"
[routerLink]="option.routerLink"
[queryParams]="option.params"
[class.active]="(currentOption$ | async)?.id === option.id"
tabindex="0">
{{ option.label | translate }}
</a>
</div>
</div>

<div class="d-block d-sm-none">
<select name="browse-type"
class="form-control"
aria-label="Browse Community or Collection"
(change)="onSelectChange($event)">
<option *ngFor="let option of allOptions"
[value]="option.id"
[attr.selected]="(currentOption$ | async)?.id === option.id ? 'selected' : null">
{{ option.label | translate }}
</option>
</select>
</div>
</nav>
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
import {
AsyncPipe,
NgForOf,
NgIf,
} from '@angular/common';
import {
Component,
Inject,
Input,
OnDestroy,
OnInit,
} from '@angular/core';
import { FormsModule } from '@angular/forms';
import {
EventType,
NavigationEnd,
Router,
RouterLink,
RouterLinkActive,
Scroll,
} from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
import {
BehaviorSubject,
combineLatest,
Observable,
Subscription,
} from 'rxjs';
import {
distinctUntilChanged,
filter,
map,
startWith,
take,
} from 'rxjs/operators';

import { getCollectionPageRoute } from '../../../../../../app/collection-page/collection-page-routing-paths';
import { getCommunityPageRoute } from '../../../../../../app/community-page/community-page-routing-paths';
import { BrowseService } from '../../../../../../app/core/browse/browse.service';
import { PaginatedList } from '../../../../../../app/core/data/paginated-list.model';
import { RemoteData } from '../../../../../../app/core/data/remote-data';
import { BrowseDefinition } from '../../../../../../app/core/shared/browse-definition.model';
import { getFirstCompletedRemoteData } from '../../../../../../app/core/shared/operators';
import { isNotEmpty } from '../../../../../../app/shared/empty.util';
import {
APP_CONFIG,
AppConfig,
} from '../../../../../../config/app-config.interface';

export interface ComColPageNavOption {
id: string;
label: string;
routerLink: string;
params?: any;
}

/**
* A component to display the "Browse By" section of a Community or Collection page
* It expects the ID of the Community or Collection as input to be passed on as a scope
*/
@Component({
selector: 'ds-themed-base-comcol-page-browse-by',
styleUrls: ['./comcol-page-browse-by.component.scss'],
templateUrl: './comcol-page-browse-by.component.html',
imports: [
FormsModule,
NgForOf,
RouterLink,
RouterLinkActive,
TranslateModule,
AsyncPipe,
NgIf,
],
standalone: true,
})
export class ComcolPageBrowseByComponent implements OnDestroy, OnInit {
/**
* The ID of the Community or Collection
*/
@Input() id: string;
@Input() contentType: string;

allOptions$: Observable<ComColPageNavOption[]>;

currentOption$: BehaviorSubject<ComColPageNavOption> = new BehaviorSubject(undefined);

subs: Subscription[] = [];

constructor(
@Inject(APP_CONFIG) public appConfig: AppConfig,
public router: Router,
private browseService: BrowseService,
) {
}

ngOnInit(): void {
this.allOptions$ = this.browseService.getBrowseDefinitions().pipe(
getFirstCompletedRemoteData(),
map((browseDefListRD: RemoteData<PaginatedList<BrowseDefinition>>) => {
const allOptions: ComColPageNavOption[] = [];
if (browseDefListRD.hasSucceeded) {
let comColRoute: string;
if (this.contentType === 'collection') {
comColRoute = getCollectionPageRoute(this.id);
allOptions.push({
id: 'search',
label: 'collection.page.browse.search.head',
routerLink: `${comColRoute}/search`,
});
} else if (this.contentType === 'community') {
comColRoute = getCommunityPageRoute(this.id);
allOptions.push({
id: 'search',
label: 'collection.page.browse.search.head',
routerLink: `${comColRoute}/search`,
});
allOptions.push({
id: 'comcols',
label: 'community.all-lists.head',
routerLink: `${comColRoute}/subcoms-cols`,
});
}

//Only browse types: title, author, dateissued are allowed for comcol pages
const allowedBrowseTypes = ['title', 'author', 'dateissued'];

allOptions.push(...browseDefListRD.payload.page
.filter((config: BrowseDefinition) => allowedBrowseTypes.includes(config.id))
.map((config: BrowseDefinition) => ({
id: `browse_${config.id}`,
label: `browse.comcol.by.${config.id}`,
routerLink: `${comColRoute}/browse/${config.id}`,
})),
);

// When the default tab is not the "search" tab, the "search" tab is moved
// at the end of the tabs ribbon for aesthetics purposes.
if (this.appConfig[this.contentType].defaultBrowseTab !== 'search') {
allOptions.push(allOptions.shift());
}
}
return allOptions;
}),
);

let comColRoute: string;
if (this.contentType === 'collection') {
comColRoute = getCollectionPageRoute(this.id);
} else if (this.contentType === 'community') {
comColRoute = getCommunityPageRoute(this.id);
}

this.subs.push(combineLatest([
this.allOptions$,
this.router.events.pipe(
startWith(this.router),
filter((next: Router|Scroll) => (isNotEmpty((next as Router)?.url) || (next as Scroll)?.type === EventType.Scroll)),
map((next: Router|Scroll) => (next as Router)?.url || ((next as Scroll).routerEvent as NavigationEnd).urlAfterRedirects),
distinctUntilChanged(),
),
]).subscribe(([navOptions, url]: [ComColPageNavOption[], string]) => {
for (const option of navOptions) {
if (url?.split('?')[0] === comColRoute && option.id === this.appConfig[this.contentType].defaultBrowseTab) {
void this.router.navigate([option.routerLink], { queryParams: option.params });
break;
} else if (option.routerLink === url?.split('?')[0]) {
this.currentOption$.next(option);
break;
}
}
}));

if (this.router.url?.split('?')[0] === comColRoute) {
this.allOptions$.pipe(
take(1),
).subscribe((allOptions: ComColPageNavOption[]) => {
for (const option of allOptions) {
if (option.id === this.appConfig[this.contentType].defaultBrowseTab) {
this.currentOption$.next(option[0]);
void this.router.navigate([option.routerLink], { queryParams: option.params });
break;
}
}
});
}
}

ngOnDestroy(): void {
this.subs.forEach((sub: Subscription) => sub.unsubscribe());
}

onSelectChange(event: any): void {
this.allOptions$.pipe(
take(1),
).subscribe((allOptions: ComColPageNavOption[]) => {
for (const option of allOptions) {
if (option.id === event.target.value) {
this.currentOption$.next(option[0]);
void this.router.navigate([option.routerLink], { queryParams: option.params });
break;
}
}
});
}
}
2 changes: 2 additions & 0 deletions src/themes/fda/eager-theme.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { HomeNewsComponent } from './app/home-page/home-news/home-news.component
import { TopLevelCommunityListComponent } from './app/home-page/top-level-community-list/top-level-community-list.component';
import { UntypedItemComponent } from './app/item-page/simple/item-types/untyped-item/untyped-item.component';
import { NavbarComponent } from './app/navbar/navbar.component';
import { ComcolPageBrowseByComponent } from './app/shared/comcol/comcol-page-browse-by/comcol-page-browse-by.component';
import { BadgesComponent } from './app/shared/object-collection/shared/badges/badges.component';
/**
* Add components that use a custom decorator to ENTRY_COMPONENTS as well as DECLARATIONS.
Expand All @@ -31,6 +32,7 @@ const DECLARATIONS = [
UntypedItemComponent,
FooterComponent,
BadgesComponent,
ComcolPageBrowseByComponent,
];

@NgModule({
Expand Down