Skip to content

Commit

Permalink
chore(null): strict null fixes for dialog-renderer deps (#6223)
Browse files Browse the repository at this point in the history
#### Details

This PR adds a bunch of `injected/visualization` files to the
strictNullCheck set. To do this, I introduced an interface seam for
`DialogRenderer` (since it has dependencies on lots of non-null-clean
components) and then did two rounds of `yarn null:find`/`yarn
null:autoadd`.

I also included a few fixes for dialog-renderer-impl that I started
making before deciding to introduce the interface seam. It is
intentionally omitted from the strictNullChecks config since it still
has non-clean dependencies.

##### Motivation

See #2869

##### Context

n/a

#### Pull request checklist
<!-- If a checklist item is not applicable to this change, write "n/a"
in the checkbox -->
- [x] Addresses an existing issue: #2869
- [x] Ran `yarn null:autoadd`
- [x] Ran `yarn fastpass`
- [x] Added/updated relevant unit test(s) (and ran `yarn test`)
- [x] Verified code coverage for the changes made. Check coverage report
at: `<rootDir>/test-results/unit/coverage`
- [x] PR title *AND* final merge commit title both start with a semantic
tag (`fix:`, `chore:`, `feat(feature-name):`, `refactor:`). See
`CONTRIBUTING.md`.
- [n/a] (UI changes only) Added screenshots/GIFs to description above
- [n/a] (UI changes only) Verified usability with NVDA/JAWS
  • Loading branch information
dbjorge authored Dec 1, 2022
1 parent 773c6c2 commit 8807c7e
Show file tree
Hide file tree
Showing 18 changed files with 101 additions and 58 deletions.
2 changes: 1 addition & 1 deletion src/injected/analyzers/tab-stops-analyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export class TabStopsAnalyzer extends BaseAnalyzer {
this.debouncedProcessTabEvents = this.debounceImpl(this.processTabEvents, 50);
this.tabStopListenerRunner.topWindowCallback = (tabEvent: TabStopEvent) => {
this.pendingTabbedElements.push(tabEvent);
this.debouncedProcessTabEvents();
this.debouncedProcessTabEvents!();
};
await this.tabStopListenerRunner.start();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
HtmlElementAxeResults,
} from 'common/types/store-data/visualization-scan-result-data';
import { WindowUtils } from 'common/window-utils';
import { DialogRenderer } from 'injected/dialog-renderer';
import {
CommandMessage,
CommandMessageResponse,
Expand All @@ -36,13 +37,7 @@ import {
} from './layered-details-dialog-component';
import { MainWindowContext } from './main-window-context';

export interface DetailsDialogWindowMessage {
data: HtmlElementAxeResults;
}

export type RenderDialog = (data: HtmlElementAxeResults) => void;

export class DialogRenderer {
export class DialogRendererImpl implements DialogRenderer {
private static readonly renderDetailsDialogCommand = 'insights.detailsDialog';

constructor(
Expand All @@ -56,16 +51,16 @@ export class DialogRenderer {
private readonly getRTLFunc: typeof getRTL,
private readonly detailsDialogHandler: DetailsDialogHandler,
) {
if (this.isInMainWindow()) {
if (this.windowUtils.isTopWindow()) {
this.frameMessenger.addMessageListener(
DialogRenderer.renderDetailsDialogCommand,
DialogRendererImpl.renderDetailsDialogCommand,
this.processRequest,
);
}
}

public render = async (data: HtmlElementAxeResults): Promise<CommandMessageResponse | null> => {
if (this.isInMainWindow()) {
if (this.windowUtils.isTopWindow()) {
const mainWindowContext = MainWindowContext.fromWindow(this.windowUtils.getWindow());
mainWindowContext.getTargetPageActionMessageCreator().openIssuesDialog();

Expand Down Expand Up @@ -121,14 +116,21 @@ export class DialogRenderer {
);
return null;
} else {
const topWindow = this.windowUtils.getTopWindow();
if (topWindow == null) {
// This is only expected if this frame's window has been detached from the
// corresponding browser navigable entity, eg because it is in the process of
// unloading. Particularly, topWindow will *not* be null merely if the top window
// is across an origin boundary. If we're in this state, we intentionally abandon
// rendering.
return null;
}

const message: CommandMessage = {
command: DialogRenderer.renderDetailsDialogCommand,
command: DialogRendererImpl.renderDetailsDialogCommand,
payload: { data: data },
};
return await this.frameMessenger.sendMessageToWindow(
this.windowUtils.getTopWindow(),
message,
);
return await this.frameMessenger.sendMessageToWindow(topWindow, message);
}
};

Expand All @@ -146,7 +148,11 @@ export class DialogRenderer {

const dialogContainer = this.dom.createElement('div');
dialogContainer.setAttribute('class', 'insights-dialog-container');
this.dom.querySelector(`#${rootContainerId}`).appendChild(dialogContainer);
const rootContainer = this.dom.querySelector(`#${rootContainerId}`);
if (rootContainer == null) {
throw new Error(`Could not find #${rootContainerId} element to inject dialog into`);
}
rootContainer.appendChild(dialogContainer);
return dialogContainer;
}

Expand All @@ -163,8 +169,4 @@ export class DialogRenderer {
private getElementSelector(data: HtmlElementAxeResults): string {
return TargetHelper.getSelectorFromTarget(data.target);
}

private isInMainWindow(): boolean {
return this.windowUtils.getTopWindow() === this.windowUtils.getWindow();
}
}
14 changes: 14 additions & 0 deletions src/injected/dialog-renderer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { HtmlElementAxeResults } from 'common/types/store-data/visualization-scan-result-data';
import { CommandMessageResponse } from 'injected/frameCommunicators/respondable-command-message-communicator';

export interface DetailsDialogWindowMessage {
data: HtmlElementAxeResults;
}

export type RenderDialog = (data: HtmlElementAxeResults) => void;

export interface DialogRenderer {
render(data: HtmlElementAxeResults): Promise<CommandMessageResponse | null>;
}
10 changes: 9 additions & 1 deletion src/injected/visualization/base-drawer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export abstract class BaseDrawer implements Drawer {
public static recalculationTimeout = 16;
protected dialogRenderer: DialogRenderer;
protected windowUtils: WindowUtils;
protected containerElement: HTMLElement;
protected containerElement: HTMLElement | null;
protected drawerUtils: DrawerUtils;

private shadowUtils: ShadowUtils;
Expand Down Expand Up @@ -105,6 +105,10 @@ export abstract class BaseDrawer implements Drawer {
};

protected applyContainerClass(): void {
if (this.containerElement == null) {
throw new Error('applyContainerClass invoked in illegal state (no containerElement)');
}

this.containerElement.setAttribute(
'class',
`insights-container insights-highlight-container ${this.containerClass}`,
Expand All @@ -127,6 +131,10 @@ export abstract class BaseDrawer implements Drawer {
}

private attachContainerToDom(): void {
if (this.containerElement == null) {
throw new Error('attachContainerToDom invoked in illegal state (no containerElement)');
}

this.shadowUtils.getShadowContainer().appendChild(this.containerElement);
}
}
8 changes: 5 additions & 3 deletions src/injected/visualization/failure-instance-formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ export abstract class FailureInstanceFormatter implements Formatter {

public abstract getDrawerConfiguration(element: Node, data: AxeResultsWithFrameLevel): any;

public abstract getDialogRenderer(): DialogRenderer;
public abstract getDialogRenderer(): DialogRenderer | null;

protected getFailureBoxConfig(data: AssessmentVisualizationInstance): FailureBoxConfig {
protected getFailureBoxConfig(
data: AssessmentVisualizationInstance,
): FailureBoxConfig | undefined {
if (data && data.isFailure) {
return FailureInstanceFormatter.failureBoxConfig;
}

return null;
return undefined;
}
}
2 changes: 1 addition & 1 deletion src/injected/visualization/formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,5 @@ export interface Formatter {
element: Node,
data: AxeResultsWithFrameLevel,
): DrawerConfiguration | SVGDrawerConfiguration | SingleTargetDrawerConfiguration;
getDialogRenderer(): DialogRenderer;
getDialogRenderer(): DialogRenderer | null;
}
2 changes: 1 addition & 1 deletion src/injected/visualization/frame-formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class FrameFormatter extends FailureInstanceFormatter {
},
};

public getDialogRenderer(): DialogRenderer {
public getDialogRenderer(): DialogRenderer | null {
return null;
}

Expand Down
10 changes: 4 additions & 6 deletions src/injected/visualization/heading-formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,15 @@ export class HeadingFormatter extends FailureInstanceFormatter {
},
};

public getDialogRenderer(): DialogRenderer {
public getDialogRenderer(): DialogRenderer | null {
return null;
}

public getDrawerConfiguration(
element: HTMLElement,
data: AssessmentVisualizationInstance,
): DrawerConfiguration {
const level = this.getAriaLevel(element)
? this.getAriaLevel(element)
: this.getHTagLevel(element);
const level = this.getAriaLevel(element) ?? this.getHTagLevel(element);
const text = (this.isHTag(element) ? 'H' : 'h') + level;
const style = HeadingFormatter.headingStyles[level] || HeadingFormatter.headingStyles.blank;

Expand Down Expand Up @@ -111,12 +109,12 @@ export class HeadingFormatter extends FailureInstanceFormatter {
return headingLevel ? headingLevel[1] : '-';
}

private getAriaLevel(element: HTMLElement): string {
private getAriaLevel(element: HTMLElement): string | null {
const attr = element.attributes.getNamedItem('aria-level');
return attr ? attr.textContent : null;
}

private getAttribute(element: HTMLElement, attrName: string): string {
private getAttribute(element: HTMLElement, attrName: string): string | null {
const attr = element.attributes.getNamedItem(attrName);
return attr ? attr.textContent : null;
}
Expand Down
3 changes: 2 additions & 1 deletion src/injected/visualization/issues-formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { getRTL } from '@fluentui/utilities';
import { NavigatorUtils } from 'common/navigator-utils';
import { HtmlElementAxeResults } from 'common/types/store-data/visualization-scan-result-data';
import { DialogRendererImpl } from 'injected/dialog-renderer-impl';
import { SingleFrameMessenger } from 'injected/frameCommunicators/single-frame-messenger';
import * as ReactDOM from 'react-dom';

Expand All @@ -28,7 +29,7 @@ export class IssuesFormatter implements Formatter {
getRTLFunc: typeof getRTL,
detailsDialogHandler: DetailsDialogHandler,
) {
this.dialogRenderer = new DialogRenderer(
this.dialogRenderer = new DialogRendererImpl(
document,
ReactDOM.render,
frameMessenger,
Expand Down
4 changes: 2 additions & 2 deletions src/injected/visualization/single-target-drawer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { SingleTargetFormatter } from './single-target-formatter';
export class SingleTargetDrawer implements Drawer {
protected isEnabled = false;
protected drawerUtils: DrawerUtils;
private target: HTMLElement;
private target: HTMLElement | null;
private formatter: SingleTargetFormatter;

constructor(drawerUtils: DrawerUtils, formatter: SingleTargetFormatter) {
Expand Down Expand Up @@ -48,7 +48,7 @@ export class SingleTargetDrawer implements Drawer {
private getFirstElementTarget(
document: Document,
elementResults: HtmlElementAxeResults[],
): HTMLElement {
): HTMLElement | null {
if (!elementResults[0]) {
return null;
}
Expand Down
4 changes: 2 additions & 2 deletions src/injected/visualization/single-target-formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export class SingleTargetFormatter implements Formatter {
return config;
}

public getDialogRenderer(): DialogRenderer {
return;
public getDialogRenderer(): DialogRenderer | null {
return null;
}
}
17 changes: 12 additions & 5 deletions src/injected/visualization/svg-shape-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ export class SVGShapeFactory {
return text;
}

private setOptionalAttribute(element: Element, attributeName: string, attributeValue?: string) {
if (attributeValue != null) {
element.setAttributeNS(null, attributeName, attributeValue);
}
}

public createFailureLabel(center: Point, failureBoxConfig: FailureBoxConfig): Element {
const myDocument = this.drawerUtils.getDocumentElement();

Expand All @@ -109,19 +115,20 @@ export class SVGShapeFactory {
box.classList.add('failure-label');
box.setAttributeNS(null, 'x', (center.x + 10).toString());
box.setAttributeNS(null, 'y', (center.y - 20).toString());
box.setAttributeNS(null, 'width', failureBoxConfig.boxWidth);
box.setAttributeNS(null, 'height', failureBoxConfig.boxWidth);
box.setAttributeNS(null, 'fill', failureBoxConfig.background);
box.setAttributeNS(null, 'rx', failureBoxConfig.cornerRadius);

this.setOptionalAttribute(box, 'width', failureBoxConfig.boxWidth);
this.setOptionalAttribute(box, 'height', failureBoxConfig.boxWidth);
this.setOptionalAttribute(box, 'rx', failureBoxConfig.cornerRadius);

const text = myDocument.createElementNS(SVGNamespaceUrl, 'text');
text.classList.add('insights-highlight-text');
text.classList.add('failure-label');
text.setAttributeNS(null, 'x', (center.x + 13.5).toString());
text.setAttributeNS(null, 'y', (center.y - 12).toString());
text.setAttributeNS(null, 'fill', failureBoxConfig.fontColor);
text.setAttributeNS(null, 'font-size', failureBoxConfig.fontSize);
text.setAttributeNS(null, 'font-weight', failureBoxConfig.fontWeight);
this.setOptionalAttribute(text, 'font-size', failureBoxConfig.fontSize);
this.setOptionalAttribute(text, 'font-weight', failureBoxConfig.fontWeight);
text.innerHTML = failureBoxConfig.text;

const group = myDocument.createElementNS(SVGNamespaceUrl, 'g');
Expand Down
2 changes: 1 addition & 1 deletion src/injected/visualization/table-headers-formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export class TableHeadersAttributeFormatter extends FailureInstanceFormatter {
super();
}

public getDialogRenderer(): DialogRenderer {
public getDialogRenderer(): DialogRenderer | null {
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { WindowUtils } from 'common/window-utils';
import { rootContainerId } from 'injected/constants';
import { DetailsDialogHandler } from 'injected/details-dialog-handler';
import { DialogRenderer } from 'injected/dialog-renderer';
import { DialogRendererImpl } from 'injected/dialog-renderer-impl';
import {
CommandMessage,
CommandMessageResponse,
Expand All @@ -31,7 +32,7 @@ import * as ReactDOM from 'react-dom';
import { IMock, It, Mock, MockBehavior, Times } from 'typemoq';
import { DictionaryStringTo } from 'types/common-types';

describe(DialogRenderer, () => {
describe(DialogRendererImpl, () => {
let htmlElementUtilsMock: IMock<HTMLElementUtils>;
let windowUtilsMock: IMock<WindowUtils>;
let navigatorUtilsMock: IMock<NavigatorUtils>;
Expand Down Expand Up @@ -267,8 +268,8 @@ describe(DialogRenderer, () => {
.setup(wum => wum.getTopWindow())
.returns(() => {
return windowStub;
})
.verifiable(Times.atLeastOnce());
});
windowUtilsMock.setup(wum => wum.isTopWindow()).returns(() => true);
windowUtilsMock.setup(wum => wum.getPlatform()).returns(() => 'Win32');
frameMessenger
.setup(fm => fm.addMessageListener(It.isValue('insights.detailsDialog'), It.isAny()))
Expand All @@ -293,10 +294,8 @@ describe(DialogRenderer, () => {
id: 'this is a different window than windowStub',
} as unknown as Window;

windowUtilsMock
.setup(wum => wum.getTopWindow())
.returns(() => topWindowStub)
.verifiable(Times.atLeastOnce());
windowUtilsMock.setup(wum => wum.getTopWindow()).returns(() => topWindowStub);
windowUtilsMock.setup(wum => wum.isTopWindow()).returns(() => false);

frameMessenger
.setup(fm => fm.addMessageListener(It.isValue(commandMessage.command), It.isAny()))
Expand Down Expand Up @@ -331,7 +330,7 @@ describe(DialogRenderer, () => {
}

function createDialogRenderer(): DialogRenderer {
return new DialogRenderer(
return new DialogRendererImpl(
domMock.object,
renderMock.object,
frameMessenger.object,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

exports[`TableHeadersAttributeFormatter getDrawerConfiguration - td headers=headers role=null id=id 1`] = `
{
"failureBoxConfig": null,
"failureBoxConfig": undefined,
"outlineColor": "#6600CC",
"outlineStyle": "solid",
"showVisualization": true,
Expand All @@ -17,7 +17,7 @@ exports[`TableHeadersAttributeFormatter getDrawerConfiguration - td headers=head

exports[`TableHeadersAttributeFormatter getDrawerConfiguration - td headers=headers role=null id=null 1`] = `
{
"failureBoxConfig": null,
"failureBoxConfig": undefined,
"outlineColor": "#6600CC",
"outlineStyle": "solid",
"showVisualization": true,
Expand All @@ -32,7 +32,7 @@ exports[`TableHeadersAttributeFormatter getDrawerConfiguration - td headers=head

exports[`TableHeadersAttributeFormatter getDrawerConfiguration - th headers=headers role=null id=id 1`] = `
{
"failureBoxConfig": null,
"failureBoxConfig": undefined,
"outlineColor": "#0066CC",
"outlineStyle": "solid",
"showVisualization": true,
Expand All @@ -47,7 +47,7 @@ exports[`TableHeadersAttributeFormatter getDrawerConfiguration - th headers=head

exports[`TableHeadersAttributeFormatter getDrawerConfiguration - th headers=null role=null id=id 1`] = `
{
"failureBoxConfig": null,
"failureBoxConfig": undefined,
"outlineColor": "#0066CC",
"outlineStyle": "solid",
"showVisualization": true,
Expand Down
Loading

0 comments on commit 8807c7e

Please sign in to comment.