feat(docs-infra): change navigation in resources page (#34756)

https://angular.io/resources needs to be sturctured to be able to navigate to all resources with improved user experience. A lone scroll bar in this page will not help the reader a great deal in exploring the resources

Fixes #33526

PR Close #34756
This commit is contained in:
ajitsinghkaler 2020-01-30 21:33:43 +05:30 committed by Misko Hevery
parent 7431206247
commit e672b1f2ac
4 changed files with 113 additions and 60 deletions

View File

@ -1,28 +1,28 @@
<div class="resources-container">
<div class="l-flex--column">
<div class="showcase" *ngFor="let category of categories">
<header class="c-resource-header">
<a class="h-anchor-offset" id="{{category.id}}"></a>
<h2>{{category.title}}</h2>
</header>
<div class="flex-center group-buttons">
<a *ngFor="let category of categories"
[class.selected]="category.id == selectedCategory.id"
class="button mat-button filter-button"
(click)="selectCategory(category.id)"
(keyup.enter)="selectCategory(category.id)">{{category.title}}</a>
</div>
<div class="l-flex--column align-items-center">
<div class="shadow-1 showcase">
<div *ngFor="let subCategory of selectedCategory?.subCategories">
<a class="h-anchor-offset" id="{{subCategory.id}}"></a>
<h3 class="subcategory-title">{{subCategory.title}}</h3>
<div class="shadow-1">
<div *ngFor="let subCategory of category.subCategories">
<a class="h-anchor-offset" id="{{subCategory.id}}"></a>
<h3 class="subcategory-title">{{subCategory.title}}</h3>
<div *ngFor="let resource of subCategory.resources">
<div class="c-resource" *ngIf="resource.rev">
<a class="l-flex--column resource-row-link" target="_blank" [href]="resource.url">
<div>
<h4>{{resource.title}}</h4>
<p class="resource-description">{{resource.desc || 'No Description'}}</p>
</div>
</a>
<div *ngFor="let resource of subCategory.resources">
<div class="c-resource" *ngIf="resource.rev">
<a class="l-flex--column resource-row-link" target="_blank" [href]="resource.url">
<div>
<h4>{{resource.title}}</h4>
<p class="resource-description">{{resource.desc || 'No Description'}}</p>
</div>
</div>
</a>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,78 +1,115 @@
import { ReflectiveInjector } from '@angular/core';
import { PlatformLocation } from '@angular/common';
import { of } from 'rxjs';
import { ResourceListComponent } from './resource-list.component';
import { ResourceService } from './resource.service';
import { LocationService } from 'app/shared/location.service';
import { Category } from './resource.model';
// Testing the component class behaviors, independent of its template
// Let e2e tests verify how it displays.
describe('ResourceListComponent', () => {
let component: ResourceListComponent;
let injector: ReflectiveInjector;
let location: TestPlatformLocation;
let resourceService: TestResourceService;
let locationService: TestLocationService;
let categories: Category[];
beforeEach(() => {
injector = ReflectiveInjector.resolveAndCreate([
ResourceListComponent,
{provide: PlatformLocation, useClass: TestPlatformLocation },
{provide: ResourceService, useClass: TestResourceService }
{provide: ResourceService, useClass: TestResourceService },
{provide: LocationService, useClass: TestLocationService }
]);
location = injector.get(PlatformLocation);
locationService = injector.get(LocationService);
resourceService = injector.get(ResourceService);
categories = resourceService.testCategories;
});
it('should set the location w/o leading slashes', () => {
location.pathname = '////resources';
const component = getComponent();
expect(component.location).toBe('resources');
it('should select the first category when no query string', () => {
component = getComponent();
expect(component.selectedCategory).toBe(categories[0]);
});
it('href(id) should return the expected href', () => {
location.pathname = '////resources';
const component = getComponent();
expect(component.href({id: 'foo'})).toBe('resources#foo');
it('should select the first category when query string w/o "category" property', () => {
locationService.searchResult = { foo: 'development' };
component = getComponent();
expect(component.selectedCategory).toBe(categories[0]);
});
it('should set scroll position to zero when no target element', () => {
const component = getComponent();
component.onScroll(undefined);
expect(component.scrollPos).toBe(0);
it('should select the first category when query category not found', () => {
locationService.searchResult = { category: 'foo' };
component = getComponent();
expect(component.selectedCategory).toBe(categories[0]);
});
it('should set scroll position to element.scrollTop when that is defined', () => {
const component = getComponent();
component.onScroll({scrollTop: 42});
expect(component.scrollPos).toBe(42);
it('should select the education category when query category is "education"', () => {
locationService.searchResult = { category: 'education' };
component = getComponent();
expect(component.selectedCategory).toBe(categories[1]);
});
it('should set scroll position to element.body.scrollTop when that is defined', () => {
const component = getComponent();
component.onScroll({body: {scrollTop: 42}});
expect(component.scrollPos).toBe(42);
it('should select the education category when query category is "EDUCATION" (case insensitive)', () => {
locationService.searchResult = { category: 'EDUCATION' };
component = getComponent();
expect(component.selectedCategory).toBe(categories[1]);
});
it('should set scroll position to 0 when no target.body.scrollTop defined', () => {
const component = getComponent();
component.onScroll({body: {}});
expect(component.scrollPos).toBe(0);
it('should set the query to the "education" category when user selects "education"', () => {
component = getComponent();
component.selectCategory('education');
expect(locationService.searchResult['category']).toBe('education');
});
it('should set the query to the first category when user selects unknown name', () => {
component = getComponent();
component.selectCategory('education'); // a legit group that isn't the first
component.selectCategory('foo'); // not a legit group name
expect(locationService.searchResult['category']).toBe('development');
});
//// Test Helpers ////
function getComponent(): ResourceListComponent { return injector.get(ResourceListComponent); }
class TestPlatformLocation {
pathname = 'resources';
function getComponent(): ResourceListComponent {
const comp = injector.get(ResourceListComponent);
comp.ngOnInit();
return comp;
}
class TestResourceService {
categories = of(getTestData);
testCategories = getTestData();
categories = of(this.testCategories);
}
interface SearchResult { [index: string]: string; }
class TestLocationService {
searchResult: SearchResult = {};
search = jasmine.createSpy('search').and.callFake(() => this.searchResult);
setSearch = jasmine.createSpy('setSearch')
.and.callFake((_label: string, result: SearchResult) => {
this.searchResult = result;
});
}
function getTestData(): Category[] {
return []; // Not interested in the data in these tests
return [
// Not interested in the sub-categories data in these tests
{
id: 'development',
title: 'Development',
order: 0,
subCategories: []
},
{
id: 'education',
title: 'Education',
order: 1,
subCategories: []
},
];
}
});

View File

@ -2,6 +2,7 @@ import { Component, OnInit } from '@angular/core';
import { Category } from './resource.model';
import { ResourceService } from './resource.service';
import { LocationService } from 'app/shared/location.service';
/* tslint:disable:template-accessibility-elements-content */
@Component({
@ -11,16 +12,27 @@ import { ResourceService } from './resource.service';
export class ResourceListComponent implements OnInit {
categories: Category[];
location: string;
selectedCategory: Category;
constructor(
private resourceService: ResourceService) {
this.location = location.pathname.replace(/^\/+/, '');
private resourceService: ResourceService,
private locationService: LocationService) {
}
ngOnInit() {
const category = this.locationService.search()['category'] || '';
// Not using async pipe because cats appear twice in template
// No need to unsubscribe because categories observable completes.
this.resourceService.categories.subscribe(cats => this.categories = cats);
this.resourceService.categories.subscribe(cats => {
this.categories = cats;
this.selectCategory(category);
});
}
selectCategory(id: string) {
id = id.toLowerCase();
this.selectedCategory =
this.categories.find(category => category.id.toLowerCase() === id) || this.categories[0];
this.locationService.setSearch('', {category: this.selectedCategory.id});
}
}

View File

@ -169,6 +169,10 @@ aio-resource-list {
flex-direction: column;
}
.align-items-center{
align-items: center;
}
.c-resource-header {
margin-bottom: 16px;
}