diff --git a/apps/rxjs.dev/src/app/custom-elements/api/api-list.component.spec.ts b/apps/rxjs.dev/src/app/custom-elements/api/api-list.component.spec.ts index 6b31361315..680b8cb830 100644 --- a/apps/rxjs.dev/src/app/custom-elements/api/api-list.component.spec.ts +++ b/apps/rxjs.dev/src/app/custom-elements/api/api-list.component.spec.ts @@ -42,6 +42,18 @@ describe('ApiListComponent', () => { }); } + /** + * Expectation Utility: Assert that filteredSections has no items + * + * Subscribes to `filteredSections` and performs expectation within subscription callback. + */ + function expectNoItems() { + component.filteredSections.subscribe(filtered => { + expect(filtered.some(section => !!section.items)).toBeFalsy(); + }); + } + + describe('#filteredSections', () => { @@ -95,11 +107,17 @@ describe('ApiListComponent', () => { }); }); - it('should have no sections and no items visible when there is no match', () => { + it('should have no sections or items visible when there is no query match', () => { component.setQuery('fizzbuzz'); - component.filteredSections.subscribe(filtered => { - expect(filtered.some(section => !!section.items)).toBeFalsy(); - }); + + expectNoItems(); + }); + + it('should have no sections or items visible when there is no non-deprecated match', () => { + component.setQuery('function_1'); + component.setIncludeDeprecated(false); + + expectNoItems(); }); }); @@ -182,6 +200,20 @@ describe('ApiListComponent', () => { expect(search.query).toBe('foo'); }); + it('should have includeDeprecated', () => { + component.setIncludeDeprecated(false); + + const search = locationService.setSearch.calls.mostRecent().args[1]; + expect(search.includeDeprecated).toBe('false'); + }); + + it('should not have includeDeprecated', () => { + component.setIncludeDeprecated(true); + + const search = locationService.setSearch.calls.mostRecent().args[1]; + expect(search.includeDeprecated).toBe(undefined); + }); + it('should keep last of multiple query settings (in lowercase)', () => { component.setQuery('foo'); component.setQuery('fooBar'); @@ -190,15 +222,17 @@ describe('ApiListComponent', () => { expect(search.query).toBe('foobar'); }); - it('should have query, status, and type', () => { + it('should have query, status, type, and includeDeprecated', () => { component.setQuery('foo'); component.setStatus({value: 'stable', title: 'Stable'}); component.setType({value: 'class', title: 'Class'}); + component.setIncludeDeprecated(false); const search = locationService.setSearch.calls.mostRecent().args[1]; expect(search.query).toBe('foo'); expect(search.status).toBe('stable'); expect(search.type).toBe('class'); + expect(search.includeDeprecated).toBe('false'); }); }); }); diff --git a/apps/rxjs.dev/src/app/custom-elements/api/api-list.component.ts b/apps/rxjs.dev/src/app/custom-elements/api/api-list.component.ts index d0eb89d3cf..be459a4d0a 100644 --- a/apps/rxjs.dev/src/app/custom-elements/api/api-list.component.ts +++ b/apps/rxjs.dev/src/app/custom-elements/api/api-list.component.ts @@ -20,6 +20,7 @@ class SearchCriteria { query? = ''; status? = 'all'; type? = 'all'; + includeDeprecated? = true; } @Component({ @@ -31,6 +32,11 @@ class SearchCriteria { search + +
@@ -62,6 +68,7 @@ export class ApiListComponent implements OnInit { status: Option; type: Option; + includeDeprecated: boolean; // API types types: Option[] = [ @@ -99,6 +106,16 @@ export class ApiListComponent implements OnInit { this.setSearchCriteria({ query: (query || '').toLowerCase().trim() }); } + // NOTE includeDeprecated is model bound - the following method is used for testing + setIncludeDeprecated(includeDeprecated: boolean) { + this.includeDeprecated = includeDeprecated; + this.onIncludeDeprecatedChange(); + } + + onIncludeDeprecatedChange() { + this.setSearchCriteria({ includeDeprecated: this.includeDeprecated }); + } + setStatus(status: Option) { this.toggleStatusMenu(); this.status = status; @@ -121,9 +138,9 @@ export class ApiListComponent implements OnInit { //////// Private ////////// - private filterSection(section: ApiSection, { query, status, type }: SearchCriteria) { + private filterSection(section: ApiSection, { query, status, type, includeDeprecated }: SearchCriteria) { const items = section.items!.filter((item) => { - return matchesType() && matchesStatus() && matchesQuery(); + return matchesType() && matchesStatus() && matchesQuery() && matchesStability(); function matchesQuery() { return !query || section.name.indexOf(query) !== -1 || item.name.indexOf(query) !== -1; @@ -136,6 +153,10 @@ export class ApiListComponent implements OnInit { function matchesType() { return type === 'all' || type === item.docType; } + + function matchesStability() { + return includeDeprecated || item.stability !== 'deprecated'; + } }); // If there are no items we still return an empty array if the section name matches and the type is 'package' @@ -144,7 +165,7 @@ export class ApiListComponent implements OnInit { // Get initial search criteria from URL search params private initializeSearchCriteria() { - const { query, status, type } = this.locationService.search(); + const { query, status, type, includeDeprecated } = this.locationService.search(); const q = (query || '').toLowerCase(); // Hack: can't bind to query because input cursor always forced to end-of-line. @@ -152,22 +173,25 @@ export class ApiListComponent implements OnInit { this.status = this.statuses.find((x) => x.value === status) || this.statuses[0]; this.type = this.types.find((x) => x.value === type) || this.types[0]; + this.includeDeprecated = includeDeprecated === 'false' ? false : true; this.searchCriteria = { query: q, status: this.status.value, type: this.type.value, + includeDeprecated: this.includeDeprecated, }; this.criteriaSubject.next(this.searchCriteria); } private setLocationSearch() { - const { query, status, type } = this.searchCriteria; + const { query, status, type, includeDeprecated } = this.searchCriteria; const params = { query: query ? query : undefined, status: status !== 'all' ? status : undefined, type: type !== 'all' ? type : undefined, + includeDeprecated: includeDeprecated ? undefined : 'false', }; this.locationService.setSearch('API Search', params); diff --git a/apps/rxjs.dev/src/app/custom-elements/api/api-list.module.ts b/apps/rxjs.dev/src/app/custom-elements/api/api-list.module.ts index aff550f37f..20abff404f 100644 --- a/apps/rxjs.dev/src/app/custom-elements/api/api-list.module.ts +++ b/apps/rxjs.dev/src/app/custom-elements/api/api-list.module.ts @@ -1,4 +1,5 @@ import { NgModule, Type } from '@angular/core'; +import { FormsModule } from '@angular/forms'; import { CommonModule } from '@angular/common'; import { HttpClientModule } from '@angular/common/http'; import { SharedModule } from '../../shared/shared.module'; @@ -7,7 +8,7 @@ import { ApiService } from './api.service'; import { WithCustomElementComponent } from '../element-registry'; @NgModule({ - imports: [ CommonModule, SharedModule, HttpClientModule ], + imports: [ CommonModule, FormsModule, SharedModule, HttpClientModule ], declarations: [ ApiListComponent ], providers: [ ApiService ] }) diff --git a/apps/rxjs.dev/src/styles/2-modules/_api-list.scss b/apps/rxjs.dev/src/styles/2-modules/_api-list.scss index 26d1a04cd7..ed0972218d 100644 --- a/apps/rxjs.dev/src/styles/2-modules/_api-list.scss +++ b/apps/rxjs.dev/src/styles/2-modules/_api-list.scss @@ -26,10 +26,16 @@ aio-api-list { margin-top: 16px; } } + + label.show-deprecated { + margin-left: auto; + width: unset; + } } .api-filter { display: flex; + align-items: center; margin: 0 auto; @media (max-width: 600px) {