refactor(docs-infra): enable tslint rules for the angular.io app (#39307)

Signed-off-by: Tasos Alexiou <tasos@arrikto.com>

PR Close #39307
This commit is contained in:
Tasos Alexiou 2020-10-16 21:46:24 +03:00 committed by Joey Perrott
parent c53bae839d
commit 356a256909
27 changed files with 88 additions and 98 deletions

View File

@ -635,61 +635,53 @@ describe('AppComponent', () => {
}); });
describe('aio-toc', () => { describe('aio-toc', () => {
let tocContainer: HTMLElement|null; function setHasFloatingTocAndGetToc(hasFloatingToc: false): [null, null];
let toc: HTMLElement|null; function setHasFloatingTocAndGetToc(hasFloatingToc: true): [HTMLElement, HTMLElement];
function setHasFloatingTocAndGetToc(hasFloatingToc: boolean) {
const setHasFloatingToc = (hasFloatingToc: boolean) => {
component.hasFloatingToc = hasFloatingToc; component.hasFloatingToc = hasFloatingToc;
fixture.detectChanges(); fixture.detectChanges();
tocContainer = fixture.debugElement.nativeElement.querySelector('.toc-container'); const tocContainer = fixture.debugElement.nativeElement.querySelector('.toc-container');
toc = tocContainer && tocContainer.querySelector('aio-toc'); const toc = tocContainer && tocContainer.querySelector('aio-toc');
};
return [toc, tocContainer];
beforeEach(() => { }
tocContainer = null;
toc = null;
});
it('should show/hide `<aio-toc>` based on `hasFloatingToc`', () => { it('should show/hide `<aio-toc>` based on `hasFloatingToc`', () => {
expect(tocContainer).toBeFalsy(); const [toc1, tocContainer1] = setHasFloatingTocAndGetToc(true);
expect(toc).toBeFalsy(); expect(tocContainer1).toBeTruthy();
expect(toc1).toBeTruthy();
setHasFloatingToc(true); const [toc2, tocContainer2] = setHasFloatingTocAndGetToc(false);
expect(tocContainer).toBeTruthy(); expect(tocContainer2).toBeFalsy();
expect(toc).toBeTruthy(); expect(toc2).toBeFalsy();
setHasFloatingToc(false);
expect(tocContainer).toBeFalsy();
expect(toc).toBeFalsy();
}); });
it('should have a non-embedded `<aio-toc>` element', () => { it('should have a non-embedded `<aio-toc>` element', () => {
setHasFloatingToc(true); const [toc] = setHasFloatingTocAndGetToc(true);
expect(toc!.classList.contains('embedded')).toBe(false); expect(toc.classList.contains('embedded')).toBe(false);
}); });
it('should update the TOC container\'s `maxHeight` based on `tocMaxHeight`', () => { it('should update the TOC container\'s `maxHeight` based on `tocMaxHeight`', () => {
setHasFloatingToc(true); const [, tocContainer] = setHasFloatingTocAndGetToc(true);
component.tocMaxHeight = '100'; component.tocMaxHeight = '100';
fixture.detectChanges(); fixture.detectChanges();
expect(tocContainer!.style.maxHeight).toBe('100px'); expect(tocContainer.style.maxHeight).toBe('100px');
component.tocMaxHeight = '200'; component.tocMaxHeight = '200';
fixture.detectChanges(); fixture.detectChanges();
expect(tocContainer!.style.maxHeight).toBe('200px'); expect(tocContainer.style.maxHeight).toBe('200px');
}); });
it('should restrain scrolling inside the ToC container', () => { it('should restrain scrolling inside the ToC container', () => {
const restrainScrolling = spyOn(component, 'restrainScrolling'); const restrainScrolling = spyOn(component, 'restrainScrolling');
const evt = new WheelEvent('wheel'); const evt = new WheelEvent('wheel');
const [, tocContainer] = setHasFloatingTocAndGetToc(true);
setHasFloatingToc(true);
expect(restrainScrolling).not.toHaveBeenCalled(); expect(restrainScrolling).not.toHaveBeenCalled();
tocContainer!.dispatchEvent(evt); tocContainer.dispatchEvent(evt);
expect(restrainScrolling).toHaveBeenCalledWith(evt); expect(restrainScrolling).toHaveBeenCalledWith(evt);
}); });
@ -697,7 +689,7 @@ describe('AppComponent', () => {
const loader = fixture.debugElement.injector.get(ElementsLoader) as unknown as TestElementsLoader; const loader = fixture.debugElement.injector.get(ElementsLoader) as unknown as TestElementsLoader;
expect(loader.loadCustomElement).not.toHaveBeenCalled(); expect(loader.loadCustomElement).not.toHaveBeenCalled();
setHasFloatingToc(true); setHasFloatingTocAndGetToc(true);
expect(loader.loadCustomElement).toHaveBeenCalledWith('aio-toc'); expect(loader.loadCustomElement).toHaveBeenCalledWith('aio-toc');
}); });
}); });
@ -721,7 +713,7 @@ describe('AppComponent', () => {
createTestingModule('a/b', 'stable'); createTestingModule('a/b', 'stable');
await initializeTest(); await initializeTest();
const banner: HTMLElement = fixture.debugElement.query(By.css('aio-mode-banner')).nativeElement; const banner: HTMLElement = fixture.debugElement.query(By.css('aio-mode-banner')).nativeElement;
expect(banner.textContent!.trim()).toEqual(''); expect(banner.textContent?.trim()).toEqual('');
}); });
}); });
@ -1355,10 +1347,10 @@ class TestHttpClient {
if (/navigation\.json/.test(url)) { if (/navigation\.json/.test(url)) {
data = this.navJson; data = this.navJson;
} else { } else {
const match = /generated\/docs\/(.+)\.json/.exec(url)!; const match = /generated\/docs\/(.+)\.json/.exec(url);
const id = match[1]!; const id = match?.[1];
// Make up a title for test purposes // Make up a title for test purposes
const title = id.split('/').pop()!.replace(/^([a-z])/, (_, letter) => letter.toUpperCase()); const title = id?.split('/')?.pop()?.replace(/^([a-z])/, (_, letter) => letter.toUpperCase());
const h1 = (id === 'no-title') ? '' : `<h1 class="no-toc">${title}</h1>`; const h1 = (id === 'no-title') ? '' : `<h1 class="no-toc">${title}</h1>`;
const contents = `${h1}<h2 id="#somewhere">Some heading</h2>`; const contents = `${h1}<h2 id="#somewhere">Some heading</h2>`;
data = { id, contents }; data = { id, contents };

View File

@ -163,7 +163,7 @@ export class AppComponent implements OnInit {
// Find the current version - eithers title matches the current deployment mode // Find the current version - eithers title matches the current deployment mode
// or its title matches the major version of the current version info // or its title matches the major version of the current version info
this.currentDocVersion = this.docVersions.find(version => this.currentDocVersion = this.docVersions.find(version =>
version.title === this.deployment.mode || version.title === `v${versionInfo.major}`)!; version.title === this.deployment.mode || version.title === `v${versionInfo.major}`) as NavigationNode;
this.currentDocVersion.title += ` (v${versionInfo.raw})`; this.currentDocVersion.title += ` (v${versionInfo.raw})`;
}); });

View File

@ -102,11 +102,11 @@ describe('AnnouncementBarComponent', () => {
}); });
it('should display an image', () => { it('should display an image', () => {
expect(element.querySelector('img')!.src).toContain('dummy/image'); expect(element.querySelector('img')?.src).toContain('dummy/image');
}); });
it('should display a link', () => { it('should display a link', () => {
expect(element.querySelector('a')!.href).toContain('link/to/website'); expect(element.querySelector('a')?.href).toContain('link/to/website');
}); });
}); });
}); });

View File

@ -38,7 +38,7 @@ describe('ApiListComponent', () => {
component.filteredSections.subscribe(filtered => { component.filteredSections.subscribe(filtered => {
filtered = filtered.filter(section => section.items); filtered = filtered.filter(section => section.items);
expect(filtered.length).toBeGreaterThan(0, 'expected something'); expect(filtered.length).toBeGreaterThan(0, 'expected something');
expect(filtered.every(section => section.items!.every(itemTest))).toBe(true, label); expect(filtered.every(section => section.items?.every(itemTest))).toBe(true, label);
}); });
} }
@ -66,7 +66,7 @@ describe('ApiListComponent', () => {
filtered = filtered.filter(section => Array.isArray(section.items)); filtered = filtered.filter(section => Array.isArray(section.items));
expect(filtered.length).toBe(1, 'only one section'); expect(filtered.length).toBe(1, 'only one section');
expect(filtered[0].name).toBe('core'); expect(filtered[0].name).toBe('core');
expect(filtered[0].items).toEqual(sections.find(section => section.name === 'core')!.items); expect(filtered[0].items).toEqual(sections.find(section => section.name === 'core')?.items as ApiItem[]);
}); });
}); });
@ -74,8 +74,8 @@ describe('ApiListComponent', () => {
it('should null if there are no matching items and the section itself does not match', () => { it('should null if there are no matching items and the section itself does not match', () => {
component.setQuery('core'); component.setQuery('core');
component.filteredSections.subscribe(filtered => { component.filteredSections.subscribe(filtered => {
const commonSection = filtered.find(section => section.name === 'common')!; const commonSection = filtered.find(section => section.name === 'common');
expect(commonSection.items).toBe(null); expect(commonSection?.items).toBe(null);
}); });
}); });
@ -117,7 +117,7 @@ describe('ApiListComponent', () => {
filtered = filtered.filter(s => s.items); filtered = filtered.filter(s => s.items);
expect(filtered.length).toBe(1, 'sections'); expect(filtered.length).toBe(1, 'sections');
expect(filtered[0].name).toBe(section, 'section name'); expect(filtered[0].name).toBe(section, 'section name');
const items = filtered[0].items!; const items = filtered[0].items as ApiItem[];
expect(items.length).toBe(1, 'items'); expect(items.length).toBe(1, 'items');
const item = items[0]; const item = items[0];

View File

@ -17,9 +17,9 @@ import { Option } from 'app/shared/select/select.component';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
class SearchCriteria { class SearchCriteria {
query ? = ''; query = '';
status ? = 'all'; status = 'all';
type ? = 'all'; type = 'all';
} }
@Component({ @Component({
@ -116,13 +116,13 @@ export class ApiListComponent implements OnInit {
const sectionNameMatches = !query || section.name.indexOf(query) !== -1; const sectionNameMatches = !query || section.name.indexOf(query) !== -1;
const matchesQuery = (item: ApiItem) => const matchesQuery = (item: ApiItem) =>
sectionNameMatches || item.name.indexOf(query!) !== -1; sectionNameMatches || item.name.indexOf(query) !== -1;
const matchesStatus = (item: ApiItem) => const matchesStatus = (item: ApiItem) =>
status === 'all' || status === item.stability || (status === 'security-risk' && item.securityRisk); status === 'all' || status === item.stability || (status === 'security-risk' && item.securityRisk);
const matchesType = (item: ApiItem) => const matchesType = (item: ApiItem) =>
type === 'all' || type === item.docType; type === 'all' || type === item.docType;
const items = section.items!.filter(item => const items: ApiItem[] = (section.items || []).filter(item =>
matchesType(item) && matchesStatus(item) && matchesQuery(item)); matchesType(item) && matchesStatus(item) && matchesQuery(item));
// If there are no items we still return an empty array if the section name matches and the type is 'package' // If there are no items we still return an empty array if the section name matches and the type is 'package'
@ -160,7 +160,7 @@ export class ApiListComponent implements OnInit {
this.locationService.setSearch('API Search', params); this.locationService.setSearch('API Search', params);
} }
private setSearchCriteria(criteria: SearchCriteria) { private setSearchCriteria(criteria: Partial<SearchCriteria>) {
this.criteriaSubject.next(Object.assign(this.searchCriteria, criteria)); this.criteriaSubject.next(Object.assign(this.searchCriteria, criteria));
this.setLocationSearch(); this.setLocationSearch();
} }

View File

@ -251,7 +251,7 @@ describe('CodeComponent', () => {
actualCode = spy.calls.mostRecent().args[0]; actualCode = spy.calls.mostRecent().args[0];
expect(actualCode).toBe(expectedCode, `when linenums=${linenums}`); expect(actualCode).toBe(expectedCode, `when linenums=${linenums}`);
expect(actualCode.match(/\r?\n/g)!.length).toBe(5); expect(actualCode.match(/\r?\n/g)?.length).toBe(5);
spy.calls.reset(); spy.calls.reset();
}); });

View File

@ -45,12 +45,12 @@ export class ElementsLoader {
loadCustomElement(selector: string): Promise<void> { loadCustomElement(selector: string): Promise<void> {
if (this.elementsLoading.has(selector)) { if (this.elementsLoading.has(selector)) {
// The custom element is in the process of being loaded and registered. // The custom element is in the process of being loaded and registered.
return this.elementsLoading.get(selector)!; return this.elementsLoading.get(selector) as Promise<void>;
} }
if (this.elementsToLoad.has(selector)) { if (this.elementsToLoad.has(selector)) {
// Load and register the custom element (for the first time). // Load and register the custom element (for the first time).
const modulePathLoader = this.elementsToLoad.get(selector)!; const modulePathLoader = this.elementsToLoad.get(selector) as LoadChildrenCallback;
const loadedAndRegistered = const loadedAndRegistered =
(modulePathLoader() as Promise<NgModuleFactory<WithCustomElementComponent> | Type<WithCustomElementComponent>>) (modulePathLoader() as Promise<NgModuleFactory<WithCustomElementComponent> | Type<WithCustomElementComponent>>)
.then(elementModuleOrFactory => { .then(elementModuleOrFactory => {
@ -73,7 +73,7 @@ export class ElementsLoader {
const CustomElementComponent = elementModuleRef.instance.customElementComponent; const CustomElementComponent = elementModuleRef.instance.customElementComponent;
const CustomElement = createCustomElement(CustomElementComponent, {injector}); const CustomElement = createCustomElement(CustomElementComponent, {injector});
customElements!.define(selector, CustomElement); customElements.define(selector, CustomElement);
return customElements.whenDefined(selector); return customElements.whenDefined(selector);
}) })
.then(() => { .then(() => {

View File

@ -337,7 +337,7 @@ describe('TocComponent', () => {
it('should re-apply the `active` class when the list elements change', () => { it('should re-apply the `active` class when the list elements change', () => {
const getActiveTextContent = () => const getActiveTextContent = () =>
page.listItems.find(By.css('.active'))!.nativeElement.textContent.trim(); page.listItems.find(By.css('.active'))?.nativeElement.textContent.trim();
tocComponent.activeIndex = 1; tocComponent.activeIndex = 1;
fixture.detectChanges(); fixture.detectChanges();

View File

@ -49,7 +49,7 @@ export class DocumentService {
if (!this.cache.has(id)) { if (!this.cache.has(id)) {
this.cache.set(id, this.fetchDocument(id)); this.cache.set(id, this.fetchDocument(id));
} }
return this.cache.get(id)!; return this.cache.get(id) as Observable<DocumentContents>;
} }
private fetchDocument(id: string): Observable<DocumentContents> { private fetchDocument(id: string): Observable<DocumentContents> {

View File

@ -213,10 +213,10 @@ describe('DocViewerComponent', () => {
describe('needed', () => { describe('needed', () => {
it('should add an embedded ToC element if there is an `<h1>` heading', () => { it('should add an embedded ToC element if there is an `<h1>` heading', () => {
doPrepareTitleAndToc(DOC_WITH_H1); doPrepareTitleAndToc(DOC_WITH_H1);
const tocEl = getTocEl()!; const tocEl = getTocEl();
expect(tocEl).toBeTruthy(); expect(tocEl).toBeTruthy();
expect(tocEl.classList.contains('embedded')).toBe(true); expect(tocEl?.classList.contains('embedded')).toBe(true);
}); });
it('should not add a second ToC element if there a hard coded one in place', () => { it('should not add a second ToC element if there a hard coded one in place', () => {

View File

@ -98,9 +98,9 @@ export class DocViewerComponent implements OnDestroy {
const needsToc = !!titleEl && !/no-?toc/i.test(titleEl.className); const needsToc = !!titleEl && !/no-?toc/i.test(titleEl.className);
const embeddedToc = targetElem.querySelector('aio-toc.embedded'); const embeddedToc = targetElem.querySelector('aio-toc.embedded');
if (needsToc && !embeddedToc) { if (titleEl && needsToc && !embeddedToc) {
// Add an embedded ToC if it's needed and there isn't one in the content already. // Add an embedded ToC if it's needed and there isn't one in the content already.
titleEl!.insertAdjacentHTML('afterend', '<aio-toc class="embedded"></aio-toc>'); titleEl.insertAdjacentHTML('afterend', '<aio-toc class="embedded"></aio-toc>');
} else if (!needsToc && embeddedToc && embeddedToc.parentNode !== null) { } else if (!needsToc && embeddedToc && embeddedToc.parentNode !== null) {
// Remove the embedded Toc if it's there and not needed. // Remove the embedded Toc if it's there and not needed.
// We cannot use ChildNode.remove() because of IE11 // We cannot use ChildNode.remove() because of IE11
@ -223,7 +223,7 @@ export class DocViewerComponent implements OnDestroy {
done$ = done$.pipe( done$ = done$.pipe(
// Remove the current view from the viewer. // Remove the current view from the viewer.
switchMap(() => animateLeave(this.currViewContainer)), switchMap(() => animateLeave(this.currViewContainer)),
tap(() => this.currViewContainer.parentElement!.removeChild(this.currViewContainer)), tap(() => (this.currViewContainer.parentElement as HTMLElement).removeChild(this.currViewContainer)),
tap(() => this.docRemoved.emit()), tap(() => this.docRemoved.emit()),
); );
} }

View File

@ -144,7 +144,7 @@ describe('NavigationService', () => {
url: 'b', url: 'b',
view: 'SideNav', view: 'SideNav',
nodes: [ nodes: [
sideNavNodes[0].children![0], sideNavNodes[0].children?.[0] as NavigationNode,
sideNavNodes[0] sideNavNodes[0]
] ]
} }
@ -156,8 +156,8 @@ describe('NavigationService', () => {
url: 'd', url: 'd',
view: 'SideNav', view: 'SideNav',
nodes: [ nodes: [
sideNavNodes[0].children![0].children![1], sideNavNodes[0].children?.[0].children?.[1] as NavigationNode,
sideNavNodes[0].children![0], sideNavNodes[0].children?.[0] as NavigationNode,
sideNavNodes[0] sideNavNodes[0]
] ]
} }
@ -201,8 +201,8 @@ describe('NavigationService', () => {
url: 'c', url: 'c',
view: 'SideNav', view: 'SideNav',
nodes: [ nodes: [
sideNavNodes[0].children![0].children![0], sideNavNodes[0].children?.[0].children?.[0] as NavigationNode,
sideNavNodes[0].children![0], sideNavNodes[0].children?.[0] as NavigationNode,
sideNavNodes[0] sideNavNodes[0]
] ]
} }

View File

@ -153,7 +153,7 @@ export class NavigationService {
if (!navMap.has(cleanedUrl)) { if (!navMap.has(cleanedUrl)) {
navMap.set(cleanedUrl, {}); navMap.set(cleanedUrl, {});
} }
const navMapItem = navMap.get(cleanedUrl)!; const navMapItem = navMap.get(cleanedUrl) as CurrentNodes;
navMapItem[view] = { url, view, nodes }; navMapItem[view] = { url, view, nodes };
} }

View File

@ -8,7 +8,7 @@ describe('Attribute Utilities', () => {
beforeEach(() => { beforeEach(() => {
const div = document.createElement('div'); const div = document.createElement('div');
div.innerHTML = `<div a b="true" c="false" D="foo" d-E></div>`; div.innerHTML = `<div a b="true" c="false" D="foo" d-E></div>`;
testEl = div.querySelector('div')!; testEl = div.querySelector('div') as HTMLElement;
}); });
describe('getAttrs', () => { describe('getAttrs', () => {

View File

@ -44,7 +44,7 @@ export class CopierService {
* @return The temporary `<textarea>` element containing the specified text. * @return The temporary `<textarea>` element containing the specified text.
*/ */
private createTextArea(text: string): HTMLTextAreaElement { private createTextArea(text: string): HTMLTextAreaElement {
const docElem = document.documentElement!; const docElem = document.documentElement;
const isRTL = docElem.getAttribute('dir') === 'rtl'; const isRTL = docElem.getAttribute('dir') === 'rtl';
// Create a temporary element to hold the contents to copy. // Create a temporary element to hold the contents to copy.

View File

@ -65,5 +65,5 @@ describe('CustomIconRegistry', () => {
function createSvg(svgSrc: string): SVGElement { function createSvg(svgSrc: string): SVGElement {
const div = document.createElement('div'); const div = document.createElement('div');
div.innerHTML = svgSrc; div.innerHTML = svgSrc;
return div.querySelector('svg')!; return div.querySelector('svg') as SVGElement;
} }

View File

@ -78,7 +78,7 @@ export class CustomIconRegistry extends MatIconRegistry {
// SECURITY: the source for the SVG icons is provided in code by trusted developers // SECURITY: the source for the SVG icons is provided in code by trusted developers
div.innerHTML = svgIcon.svgSource; div.innerHTML = svgIcon.svgSource;
const svgElement = div.querySelector('svg')!; const svgElement = div.querySelector('svg') as SVGElement;
nsIconMap[svgIcon.name] = svgElement; nsIconMap[svgIcon.name] = svgElement;
return svgElement; return svgElement;

View File

@ -235,7 +235,7 @@ describe('ScrollSpyService', () => {
it('should remember and emit the last active item to new subscribers', () => { it('should remember and emit the last active item to new subscribers', () => {
const items = [{index: 1}, {index: 2}, {index: 3}] as ScrollItem[]; const items = [{index: 1}, {index: 2}, {index: 3}] as ScrollItem[];
let lastActiveItem: ScrollItem|null; let lastActiveItem = null as unknown as ScrollItem|null;
const info = scrollSpyService.spyOn([]); const info = scrollSpyService.spyOn([]);
const spiedElemGroup = getSpiedElemGroups()[0]; const spiedElemGroup = getSpiedElemGroups()[0];
@ -247,12 +247,12 @@ describe('ScrollSpyService', () => {
spiedElemGroup.activeScrollItem.next(items[1]); spiedElemGroup.activeScrollItem.next(items[1]);
info.active.subscribe(item => lastActiveItem = item); info.active.subscribe(item => lastActiveItem = item);
expect(lastActiveItem!).toBe(items[1]); expect(lastActiveItem).toBe(items[1]);
spiedElemGroup.activeScrollItem.next(null); spiedElemGroup.activeScrollItem.next(null);
info.active.subscribe(item => lastActiveItem = item); info.active.subscribe(item => lastActiveItem = item);
expect(lastActiveItem!).toBeNull(); expect(lastActiveItem).toBeNull();
}); });
it('should only emit distinct values on `active`', () => { it('should only emit distinct values on `active`', () => {

View File

@ -114,7 +114,7 @@ describe('ScrollService', () => {
it('should not break when cookies are disabled in the browser', () => { it('should not break when cookies are disabled in the browser', () => {
expect(() => { expect(() => {
const originalSessionStorage = Object.getOwnPropertyDescriptor(window, 'sessionStorage')!; const originalSessionStorage = Object.getOwnPropertyDescriptor(window, 'sessionStorage') as PropertyDescriptor;
try { try {
// Simulate `window.sessionStorage` being inaccessible, when cookies are disabled. // Simulate `window.sessionStorage` being inaccessible, when cookies are disabled.

View File

@ -33,7 +33,7 @@ export class ScrollService implements OnDestroy {
const toolbar = this.document.querySelector('.app-toolbar'); const toolbar = this.document.querySelector('.app-toolbar');
this._topOffset = (toolbar && toolbar.clientHeight || 0) + topMargin; this._topOffset = (toolbar && toolbar.clientHeight || 0) + topMargin;
} }
return this._topOffset!; return this._topOffset as number;
} }
get topOfPageElement() { get topOfPageElement() {

View File

@ -98,10 +98,10 @@ function splitPages(allPages: SearchResult[]) {
} }
}); });
while (priorityPages.length < 5 && pages.length) { while (priorityPages.length < 5 && pages.length) {
priorityPages.push(pages.shift()!); priorityPages.push(pages.shift() as SearchResult);
} }
while (priorityPages.length < 5 && deprecated.length) { while (priorityPages.length < 5 && deprecated.length) {
priorityPages.push(deprecated.shift()!); priorityPages.push(deprecated.shift() as SearchResult);
} }
pages.sort(compareResults); pages.sort(compareResults);

View File

@ -35,10 +35,10 @@ describe('SelectComponent', () => {
describe('button', () => { describe('button', () => {
it('should display the label if provided', () => { it('should display the label if provided', () => {
expect(getButton().textContent!.trim()).toEqual(''); expect(getButton().textContent?.trim()).toEqual('');
host.label = 'Label:'; host.label = 'Label:';
fixture.detectChanges(); fixture.detectChanges();
expect(getButton().textContent!.trim()).toEqual('Label:'); expect(getButton().textContent?.trim()).toEqual('Label:');
}); });
it('should contain a symbol if hasSymbol is true', () => { it('should contain a symbol if hasSymbol is true', () => {
@ -53,7 +53,7 @@ describe('SelectComponent', () => {
host.selected = options[0]; host.selected = options[0];
fixture.detectChanges(); fixture.detectChanges();
expect(getButton().textContent).toContain(options[0].title); expect(getButton().textContent).toContain(options[0].title);
expect(getButtonSymbol()!.className).toContain(options[0].value); expect(getButtonSymbol()?.className).toContain(options[0].value);
}); });
it('should toggle the visibility of the options list when clicked', () => { it('should toggle the visibility of the options list when clicked', () => {
@ -108,7 +108,7 @@ describe('SelectComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
expect(host.onChange).toHaveBeenCalledWith({ option: options[0], index: 0 }); expect(host.onChange).toHaveBeenCalledWith({ option: options[0], index: 0 });
expect(getButton().textContent).toContain(options[0].title); expect(getButton().textContent).toContain(options[0].title);
expect(getButtonSymbol()!.className).toContain(options[0].value); expect(getButtonSymbol()?.className).toContain(options[0].value);
}); });
it('should select the current option when enter is pressed', () => { it('should select the current option when enter is pressed', () => {
@ -117,7 +117,7 @@ describe('SelectComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
expect(host.onChange).toHaveBeenCalledWith({ option: options[0], index: 0 }); expect(host.onChange).toHaveBeenCalledWith({ option: options[0], index: 0 });
expect(getButton().textContent).toContain(options[0].title); expect(getButton().textContent).toContain(options[0].title);
expect(getButtonSymbol()!.className).toContain(options[0].value); expect(getButtonSymbol()?.className).toContain(options[0].value);
}); });
it('should select the current option when space is pressed', () => { it('should select the current option when space is pressed', () => {
@ -126,7 +126,7 @@ describe('SelectComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
expect(host.onChange).toHaveBeenCalledWith({ option: options[0], index: 0 }); expect(host.onChange).toHaveBeenCalledWith({ option: options[0], index: 0 });
expect(getButton().textContent).toContain(options[0].title); expect(getButton().textContent).toContain(options[0].title);
expect(getButtonSymbol()!.className).toContain(options[0].value); expect(getButtonSymbol()?.className).toContain(options[0].value);
}); });
it('should hide when an option is clicked', () => { it('should hide when an option is clicked', () => {

View File

@ -237,23 +237,23 @@ describe('TocService', () => {
}); });
it('should have href with docId and heading\'s id', () => { it('should have href with docId and heading\'s id', () => {
const tocItem = lastTocList.find(item => item.title === 'Heading one')!; const tocItem = lastTocList.find(item => item.title === 'Heading one');
expect(tocItem.href).toEqual(`${docId}#heading-one-special-id`); expect(tocItem?.href).toEqual(`${docId}#heading-one-special-id`);
}); });
it('should have level "h1" for an <h1>', () => { it('should have level "h1" for an <h1>', () => {
const tocItem = lastTocList.find(item => item.title === 'Fun with TOC')!; const tocItem = lastTocList.find(item => item.title === 'Fun with TOC');
expect(tocItem.level).toEqual('h1'); expect(tocItem?.level).toEqual('h1');
}); });
it('should have level "h2" for an <h2>', () => { it('should have level "h2" for an <h2>', () => {
const tocItem = lastTocList.find(item => item.title === 'Heading one')!; const tocItem = lastTocList.find(item => item.title === 'Heading one');
expect(tocItem.level).toEqual('h2'); expect(tocItem?.level).toEqual('h2');
}); });
it('should have level "h3" for an <h3>', () => { it('should have level "h3" for an <h3>', () => {
const tocItem = lastTocList.find(item => item.title === 'H3 3a')!; const tocItem = lastTocList.find(item => item.title === 'H3 3a');
expect(tocItem.level).toEqual('h3'); expect(tocItem?.level).toEqual('h3');
}); });
it('should have title which is heading\'s textContent ', () => { it('should have title which is heading\'s textContent ', () => {
@ -275,8 +275,8 @@ describe('TocService', () => {
}); });
it('should have href with docId and calculated heading id', () => { it('should have href with docId and calculated heading id', () => {
const tocItem = lastTocList.find(item => item.title === 'H2 Two')!; const tocItem = lastTocList.find(item => item.title === 'H2 Two');
expect(tocItem.href).toEqual(`${docId}#h2-two`); expect(tocItem?.href).toEqual(`${docId}#h2-two`);
}); });
it('should ignore HTML in heading when calculating id', () => { it('should ignore HTML in heading when calculating id', () => {

View File

@ -70,7 +70,7 @@ export class TocService {
// Remove any remaining `a` elements (but keep their content). // Remove any remaining `a` elements (but keep their content).
querySelectorAll(div, 'a').forEach(anchorLink => { querySelectorAll(div, 'a').forEach(anchorLink => {
// We want to keep the content of this anchor, so move it into its parent. // We want to keep the content of this anchor, so move it into its parent.
const parent = anchorLink.parentNode!; const parent = anchorLink.parentNode as Node;
while (anchorLink.childNodes.length) { while (anchorLink.childNodes.length) {
parent.insertBefore(anchorLink.childNodes[0], anchorLink); parent.insertBefore(anchorLink.childNodes[0], anchorLink);
} }

View File

@ -5,7 +5,7 @@ export class MockLocationService {
urlSubject = new BehaviorSubject<string>(this.initialUrl); urlSubject = new BehaviorSubject<string>(this.initialUrl);
currentUrl = this.urlSubject.asObservable().pipe(map(url => this.stripSlashes(url))); currentUrl = this.urlSubject.asObservable().pipe(map(url => this.stripSlashes(url)));
// strip off query and hash // strip off query and hash
currentPath = this.currentUrl.pipe(map(url => url.match(/[^?#]*/)![0])); currentPath = this.currentUrl.pipe(map(url => url.match(/[^?#]*/)?.[0] || ''));
search = jasmine.createSpy('search').and.returnValue({}); search = jasmine.createSpy('search').and.returnValue({});
setSearch = jasmine.createSpy('setSearch'); setSearch = jasmine.createSpy('setSearch');
fullPageNavigationNeeded = jasmine.createSpy('Location.fullPageNavigationNeeded'); fullPageNavigationNeeded = jasmine.createSpy('Location.fullPageNavigationNeeded');
@ -23,4 +23,3 @@ export class MockLocationService {
return url.replace(/^\/+/, '').replace(/\/+(\?|#|$)/, '$1'); return url.replace(/^\/+/, '').replace(/\/+(\?|#|$)/, '$1');
} }
} }

View File

@ -36,7 +36,7 @@ describe('site App', () => {
// Test all headings (and sub-headings). // Test all headings (and sub-headings).
expect(await navItemHeadings.count()).toBeGreaterThan(0); expect(await navItemHeadings.count()).toBeGreaterThan(0);
await navItemHeadings.each(heading => testNavItemHeading(heading!, 1)); await navItemHeadings.each(heading => heading && testNavItemHeading(heading, 1));
// Helpers // Helpers
async function expectToBeCollapsed(elementFinder: ElementFinder) { async function expectToBeCollapsed(elementFinder: ElementFinder) {
@ -63,7 +63,7 @@ describe('site App', () => {
// Recursively test child-headings (while this heading is expanded). // Recursively test child-headings (while this heading is expanded).
const nextLevel = level + 1; const nextLevel = level + 1;
const childNavItemHeadings = page.getNavItemHeadings(children, nextLevel); const childNavItemHeadings = page.getNavItemHeadings(children, nextLevel);
await childNavItemHeadings.each(childHeading => testNavItemHeading(childHeading!, nextLevel)); await childNavItemHeadings.each(childHeading => childHeading && testNavItemHeading(childHeading, nextLevel));
// Ensure heading does not cause navigation when collapsing. // Ensure heading does not cause navigation when collapsing.
await page.click(heading); await page.click(heading);

View File

@ -61,8 +61,7 @@
true, true,
"ignore-params" "ignore-params"
], ],
// TODO(gkalpak): Fix the code and enable this to align with CLI. (Failures: 59) "no-non-null-assertion": true,
// "no-non-null-assertion": true,
"no-redundant-jsdoc": true, "no-redundant-jsdoc": true,
"no-switch-case-fall-through": true, "no-switch-case-fall-through": true,
"no-var-requires": false, "no-var-requires": false,