Skip to content

Commit c21b667

Browse files
committed
enable strict typechecking for UI
1 parent 9c0b118 commit c21b667

File tree

11 files changed

+75
-63
lines changed

11 files changed

+75
-63
lines changed

ui/package-lock.json

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ui/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@
2020
},
2121
"devDependencies": {
2222
"@types/enzyme": "^3.1.15",
23+
"@types/enzyme-adapter-react-16": "^1.0.3",
2324
"@types/jest": "^23.3.13",
2425
"@types/reach__router": "^1.2.3",
2526
"@types/react": "^16.7.20",
2627
"@types/react-dom": "^16.0.11",
28+
"@types/react-test-renderer": "^16.0.3",
2729
"awesome-typescript-loader": "^5.2.1",
2830
"css-loader": "^1.0.1",
2931
"dotenv": "^6.2.0",

ui/src/components/App.tsx

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { Router } from '@reach/router'
2-
import * as Msal from 'msal'
2+
import { UserAgentApplication } from 'msal'
33
import * as React from 'react'
44

5+
import './../styles/App.css'
56
import { AuthProvider } from './AuthContext'
67
import { AuthResponseBar } from './AuthResponseBar'
78
import { DefaultComponent } from './DefaultComponent'
@@ -10,16 +11,14 @@ import { Navbar } from './Navbar'
1011
import { Person } from './Person'
1112
import { Title } from './Title'
1213

13-
import './../styles/App.css'
14-
1514
// Webpack will automatically replace this variable during build time
1615
const appConfig = {
1716
clientID: '', // defaulted to '' when no OAuth client id is passed in
1817
}
1918

2019
// initialize the UserAgentApplication globally so popup and iframe can run in the background
2120
// short circuit userAgentApp. If clientID is null so is userAgentApp
22-
const userAgentApp = appConfig.clientID && new Msal.UserAgentApplication(appConfig.clientID, null, null)
21+
const userAgentApp = appConfig.clientID && new UserAgentApplication(appConfig.clientID, null, () => null)
2322

2423
// webpack replaces this variable on build time
2524
let basepath = process.env.WEBPACK_PROP_UI_BASEPATH || '' // default to empty string for testing
@@ -45,18 +44,28 @@ export class App extends React.Component {
4544
console.warn('AAD Client ID has not been configured. If you are currently in production mode, see the \'deploy\' documentation for details on how to fix this.')
4645
this.setState({ accessToken: '' })
4746
}
48-
} else { // normal or 'production' auth flow
47+
} else if (userAgentApp === '') { // normal or 'production' auth flow
48+
alert('userAgentApp not initialized')
49+
} else {
4950
let accessToken = null
5051
try {
5152
if (this.state.accessToken !== null) {
5253
// log out
53-
await userAgentApp.logout()
54+
if (userAgentApp) {
55+
await userAgentApp.logout()
56+
} else {
57+
alert('userAgentApp not initialized')
58+
}
5459
} else {
5560
// log in
5661
const graphScopes = [appConfig.clientID]
57-
await userAgentApp.loginPopup(graphScopes)
58-
accessToken = await userAgentApp.acquireTokenSilent(graphScopes,
59-
'https://login.microsoftonline.com/microsoft.onmicrosoft.com')
62+
if (userAgentApp) {
63+
await userAgentApp.loginPopup(graphScopes)
64+
accessToken = await userAgentApp.acquireTokenSilent(graphScopes,
65+
'https://login.microsoftonline.com/microsoft.onmicrosoft.com')
66+
} else {
67+
alert('userAgentApp not initialized')
68+
}
6069
}
6170
this.setState({ accessToken })
6271
} catch (err) {
@@ -75,12 +84,7 @@ export class App extends React.Component {
7584
// by linking the accessToken to the app state we can be certain the
7685
// context will always update and propogate the value to subscribed nodes
7786
return (
78-
<AuthProvider value={{
79-
accessToken: this.state.accessToken,
80-
authResponse: this.state.authResponse,
81-
handleAuth: this.handleAuth,
82-
setAuthResponse: this.setAuthResponse,
83-
}}>
87+
<AuthProvider value={{handleAuth: this.handleAuth}}>
8488
<div className='app-container'>
8589
<Navbar basepath={basepath} />
8690
<AuthResponseBar />

ui/src/components/AuthContext.tsx

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,14 @@
11
import { createContext } from 'react'
22

33
export interface IAuthContextValues {
4-
accessToken: string,
5-
authResponse: string,
6-
handleAuth: () => void,
7-
setAuthResponse: (msg?: string) => void,
4+
accessToken?: string,
5+
authResponse?: string,
6+
handleAuth?: () => any,
7+
setAuthResponse?: (msg?: string) => any,
88
}
99

1010
// Default values and enforce use of interface
11-
const defaultAuthContextValue: IAuthContextValues = {
12-
accessToken: null,
13-
authResponse: null,
14-
handleAuth: () => null,
15-
setAuthResponse: () => null,
16-
}
11+
const defaultAuthContextValue: IAuthContextValues = {}
1712

1813
export const AuthContext = createContext<IAuthContextValues>(defaultAuthContextValue)
1914
// exporting the Provider and Consumer components for more specific imports

ui/src/components/Navbar.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import { Link } from '@reach/router'
22
import * as React from 'react'
33

4-
import { AuthButton } from './AuthButton'
5-
64
import './../styles/Navbar.css'
5+
import { AuthButton } from './AuthButton'
76

8-
const isActive = ({ isCurrent }) => {
7+
const isActive = ({ isCurrent }: {isCurrent: boolean}) => {
98
return {
109
className: isCurrent ? 'nav-link active' : 'nav-link',
1110
}

ui/src/components/PageForm.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import * as React from 'react'
33
export interface IPageFormProps {
44
inputTitle: string,
55
inputPlaceholder: string,
6-
onInputChange: (event) => void,
7-
onSubmitClick: (event) => void,
6+
onInputChange: (event: React.ChangeEvent<HTMLInputElement>) => any,
7+
onSubmitClick: (event: React.MouseEvent<HTMLInputElement, MouseEvent>) => any,
88
}
99

1010
export function PageForm(props: IPageFormProps) {

ui/src/components/Person.tsx

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { PageForm } from './PageForm'
55

66
export interface IPersonProps { path: string }
77

8-
export interface IPersonState { loading: boolean, personId: string, result: object }
8+
export interface IPersonState { loading: boolean, personId?: string, result?: object }
99

1010
export interface IPersonResponse extends Response {
1111
_embedded?: { persons: Array<{ nconst: string }> },
@@ -18,10 +18,8 @@ export interface IPersonResult { nconst: string }
1818
export class Person extends React.Component<IPersonProps, IPersonState> {
1919
public static contextType = AuthContext
2020

21-
public state = {
21+
public state: IPersonState = {
2222
loading: false,
23-
personId: null,
24-
result: null,
2523
}
2624

2725
public render() {
@@ -47,12 +45,11 @@ export class Person extends React.Component<IPersonProps, IPersonState> {
4745
)
4846
}
4947

50-
private handleNameInputChange = (event) => this.setState({
48+
private handleNameInputChange = (event: React.ChangeEvent<HTMLInputElement>) => this.setState({
5149
personId: event.target.value,
52-
result: null,
5350
})
5451

55-
private handleFormSubmit = async (event) => {
52+
private handleFormSubmit = async (event: React.MouseEvent<HTMLInputElement>) => {
5653
event.preventDefault()
5754

5855
this.setState({ loading: true })
@@ -75,11 +72,11 @@ export class Person extends React.Component<IPersonProps, IPersonState> {
7572
const response = await fetch(endpoint, options)
7673
const resOut: IPersonResponse = await response.json()
7774

78-
if (resOut.hasOwnProperty('error')) {
75+
if (resOut.error) {
7976
throw new Error(`Error: ${resOut.error}, ${resOut.error_description}`)
8077
} else if (id) {
8178
this.setState({ result: resOut })
82-
} else {
79+
} else if (resOut._embedded) {
8380
const persons = resOut._embedded.persons
8481
const result: IPersonResult = persons[Math.floor(Math.random() * persons.length)]
8582
this.setState({ result, personId: result.nconst })

ui/src/components/PrivateRoute.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export class PrivateRoute extends React.Component<IPrivateRouteProps> {
1414
}
1515
}
1616

17-
public componentDidUpdate(prevProps, prevState, snapshot) {
17+
public componentDidUpdate(prevProps: IPrivateRouteProps) {
1818
if (this.context.accessToken === null && this.props.path !== prevProps.path) {
1919
this.context.setAuthResponse(`Please log in to access: ${this.props.path}`)
2020
} else if (this.context.authResponse !== null && this.context.accessToken !== null) {

ui/src/components/Title.tsx

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { PageForm } from './PageForm'
55

66
export interface ITitleProps { path: string }
77

8-
export interface ITitleState { loading: boolean, titleId: string, result: object }
8+
export interface ITitleState { loading: boolean, titleId?: string, result?: object }
99

1010
export interface ITitleResponse extends Response {
1111
_embedded?: { titles: Array<{ tconst: string }> },
@@ -18,10 +18,8 @@ export interface ITitleResult { tconst: string }
1818
export class Title extends React.Component<ITitleProps, ITitleState> {
1919
public static contextType = AuthContext
2020

21-
public state = {
21+
public state: ITitleState = {
2222
loading: false,
23-
result: null,
24-
titleId: null,
2523
}
2624

2725
public render() {
@@ -47,12 +45,13 @@ export class Title extends React.Component<ITitleProps, ITitleState> {
4745
)
4846
}
4947

50-
private handleNameInputChange = (event) => this.setState({
51-
result: null,
52-
titleId: event.target.value,
53-
})
48+
private handleNameInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
49+
this.setState({
50+
titleId: event.target.value,
51+
})
52+
}
5453

55-
private handleFormSubmit = async (event) => {
54+
private handleFormSubmit = async (event: React.MouseEvent<HTMLInputElement>) => {
5655
event.preventDefault()
5756

5857
this.setState({ loading: true })
@@ -79,10 +78,13 @@ export class Title extends React.Component<ITitleProps, ITitleState> {
7978
throw new Error(`Error: ${resOut.error}, ${resOut.error_description}`)
8079
} else if (id) {
8180
this.setState({ result: resOut })
82-
} else {
81+
} else if (resOut._embedded) {
8382
const titles = resOut._embedded.titles
8483
const result: ITitleResult = titles[Math.floor(Math.random() * titles.length)]
8584
this.setState({ result, titleId: result.tconst })
85+
} else {
86+
throw new Error('Response does not match expected format. _embedded not found\n' +
87+
'Received: ${JSON.stringify(resOut)}')
8688
}
8789
} catch (err) {
8890
this.setState({ result: { error: err.message } })

ui/tsconfig.json

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,12 @@
33
"outDir": "./dist/",
44
"module": "commonjs",
55
"target": "es5",
6+
"strict": true,
67
"jsx": "react",
7-
"lib": [ "es2015", "dom" ],
8+
"lib": ["es2015", "dom"],
89
"allowSyntheticDefaultImports": true,
9-
"typeRoots": [
10-
"./node_modules/@types",
11-
"./types"
12-
]
10+
"typeRoots": ["./node_modules/@types", "./types"]
1311
},
14-
"exclude": [
15-
"./node_modules/**/*"
16-
],
17-
"include": [
18-
"./src/**/*"
19-
]
20-
}
12+
"exclude": ["./node_modules/**/*"],
13+
"include": ["./src/**/*"]
14+
}

0 commit comments

Comments
 (0)