-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add components for different states of the API response #1001
Open
lindseydew
wants to merge
8
commits into
vb-ld/add-saved-article-routing-behind-switch
Choose a base branch
from
ld-display-saved-articles
base: vb-ld/add-saved-article-routing-behind-switch
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
2a75263
Add mechanism to override Cypress feature switch state
vlbee d988ab7
Display an error page if S4L call fails
lindseydew 9294192
Define a test for displaying articles
lindseydew 2f99d4e
Implement displaying the articles from S4L call
lindseydew d98f50a
Display an error page if unexpected response from S4L API
lindseydew 5e4cd53
Add empty state page for when a user has no saved articles
lindseydew 309dce1
Add a loading state
lindseydew bed6733
Wait to do typechecking when we have a schema
lindseydew File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
110 changes: 110 additions & 0 deletions
110
client/__tests__/components/savedarticles/savedArticlesPage.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import { render, screen, waitFor } from '@testing-library/react'; | ||
import '@testing-library/jest-dom'; | ||
import { SavedArticlesPage } from '../../../components/mma/savedArticles/SavedArticles'; | ||
// Importing this in order to create the Response object, as per the advice of this | ||
// https://stackoverflow.com/questions/52420318/referenceerror-response-is-not-defined | ||
import 'isomorphic-fetch'; | ||
|
||
describe('SavedArticlesPage', () => { | ||
const successfulAPIResponse: (body: string) => Response = (body) => { | ||
return new Response(body, { | ||
status: 200, | ||
statusText: 'Ok', | ||
headers: { | ||
'Content-type': 'application/json', | ||
}, | ||
}); | ||
}; | ||
it('should show an error page if the S4L API call fails', async () => { | ||
render( | ||
<SavedArticlesPage | ||
saveForLaterAPICall={() => { | ||
return Promise.reject(); | ||
}} | ||
/>, | ||
); | ||
|
||
await waitFor(() => { | ||
expect(screen.queryByText('Oops!')).toBeInTheDocument(); | ||
}); | ||
}); | ||
|
||
it('should display a list of articles if the S4L API call succeeds and user has saved articles', async () => { | ||
const articles = { | ||
version: '1', | ||
articles: [ | ||
{ | ||
id: 'world/2018/mar/08/donald-trump-north-korea-kim-jong-un-meeting-may-letter-invite-talks-nuclear-weapons', | ||
shortUrl: '/p/88btx', | ||
date: '2018-03-09T14:08:02Z', | ||
read: false, | ||
}, | ||
{ | ||
id: 'football/2018/mar/12/jamie-carragher-dropped-by-danish-broadcaster-after-spitting-incident', | ||
shortUrl: '/p/88qhj', | ||
date: '2018-03-12T16:53:32Z', | ||
read: false, | ||
}, | ||
], | ||
}; | ||
|
||
const apiResponse = successfulAPIResponse(JSON.stringify(articles)); | ||
|
||
render( | ||
<SavedArticlesPage | ||
saveForLaterAPICall={() => { | ||
return Promise.resolve(apiResponse); | ||
}} | ||
/>, | ||
); | ||
await waitFor(() => { | ||
expect( | ||
screen.queryByText( | ||
'world/2018/mar/08/donald-trump-north-korea-kim-jong-un-meeting-may-letter-invite-talks-nuclear-weapons', | ||
), | ||
).toBeInTheDocument(); | ||
}); | ||
}); | ||
|
||
// eslint-disable-next-line jest/no-disabled-tests -- Add this test in when we have validated the response type | ||
it.skip('should display an error message if the API call is successful but the json response is not expected', async () => { | ||
const invalidJson = { | ||
foo: 'bar', | ||
}; | ||
|
||
const apiResponse = successfulAPIResponse(JSON.stringify(invalidJson)); | ||
|
||
render( | ||
<SavedArticlesPage | ||
saveForLaterAPICall={() => { | ||
return Promise.resolve(apiResponse); | ||
}} | ||
/>, | ||
); | ||
|
||
await waitFor(() => { | ||
expect(screen.queryByText('Oops!')).toBeInTheDocument(); | ||
}); | ||
}); | ||
|
||
it('should display the empty articles page if user has no saved articles', async () => { | ||
const noArticles = { | ||
version: 1, | ||
articles: [], | ||
}; | ||
|
||
const apiResponse = successfulAPIResponse(JSON.stringify(noArticles)); | ||
render( | ||
<SavedArticlesPage | ||
saveForLaterAPICall={() => { | ||
return Promise.resolve(apiResponse); | ||
}} | ||
/>, | ||
); | ||
await waitFor(() => { | ||
expect( | ||
screen.queryByText('You have no saved articles'), | ||
).toBeInTheDocument(); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,61 @@ | ||
import { typeCheckObject } from '../../../../shared/typeCheckObject'; | ||
import { fetchWithDefaultParameters } from '../../../utilities/fetch'; | ||
import { | ||
LoadingState, | ||
useAsyncLoader, | ||
} from '../../../utilities/hooks/useAsyncLoader'; | ||
import { GenericErrorScreen } from '../../shared/GenericErrorScreen'; | ||
import { NAV_LINKS } from '../../shared/nav/NavConfig'; | ||
import { PageContainer } from '../Page'; | ||
import { JsonResponseHandler } from '../shared/asyncComponents/DefaultApiResponseHandler'; | ||
import { isSavedArticlesResponse } from './models/SavedArticle'; | ||
import type { SavedArticlesResponse } from './models/SavedArticle'; | ||
import { SavedArticlesDisplay } from './SavedArticlesDisplay'; | ||
import { SavedArticlesEmpty } from './SavedArticlesEmpty'; | ||
import { SavedArticlesLoading } from './SavedArticlesLoading'; | ||
|
||
export const SavedArticles = () => { | ||
return ( | ||
<PageContainer | ||
selectedNavItem={NAV_LINKS.savedArticles} | ||
pageTitle={NAV_LINKS.savedArticles.title} | ||
> | ||
<p>There is nothing to see here yet</p> | ||
<SavedArticlesPage | ||
saveForLaterAPICall={() => | ||
fetchWithDefaultParameters('/add/url') | ||
} | ||
/> | ||
</PageContainer> | ||
); | ||
}; | ||
|
||
interface SavedArticlesPageProps { | ||
saveForLaterAPICall: () => Promise<unknown>; | ||
} | ||
|
||
export function SavedArticlesPage(props: SavedArticlesPageProps) { | ||
const { | ||
data, | ||
loadingState, | ||
}: { | ||
data: unknown; | ||
loadingState: LoadingState; | ||
} = useAsyncLoader(props.saveForLaterAPICall, JsonResponseHandler); | ||
|
||
if (loadingState === LoadingState.IsLoading) { | ||
return <SavedArticlesLoading />; | ||
} | ||
if (loadingState === LoadingState.HasError) { | ||
return <GenericErrorScreen />; | ||
} | ||
const saveForLaterData = typeCheckObject<SavedArticlesResponse>( | ||
isSavedArticlesResponse, | ||
)(data); | ||
if (saveForLaterData === undefined) { | ||
return <GenericErrorScreen />; | ||
} | ||
if (saveForLaterData.articles.length === 0) { | ||
return <SavedArticlesEmpty />; | ||
} | ||
return <SavedArticlesDisplay savedArticles={saveForLaterData.articles} />; | ||
} |
File renamed without changes.
15 changes: 15 additions & 0 deletions
15
client/components/mma/savedArticles/SavedArticlesDisplay.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import type { SavedArticle } from './models/SavedArticle'; | ||
|
||
interface SavedArticlesProps { | ||
savedArticles: SavedArticle[]; | ||
} | ||
|
||
export const SavedArticlesDisplay = ({ savedArticles }: SavedArticlesProps) => { | ||
return ( | ||
<> | ||
{savedArticles.map((article) => { | ||
return <li key={article.id}>{article.id}</li>; | ||
})} | ||
</> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export const SavedArticlesEmpty = () => { | ||
return <p>You have no saved articles</p>; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { Spinner } from '../../shared/Spinner'; | ||
import { WithStandardTopMargin } from '../../shared/WithStandardTopMargin'; | ||
|
||
export const SavedArticlesLoading = () => ( | ||
<WithStandardTopMargin> | ||
<Spinner loadingMessage={'Fetching saved articles'} /> | ||
</WithStandardTopMargin> | ||
); |
18 changes: 18 additions & 0 deletions
18
client/components/mma/savedArticles/models/SavedArticle.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { isObject } from '@guardian/libs'; | ||
|
||
export interface SavedArticle { | ||
id: string; | ||
shortUrl: string; | ||
date: Date; | ||
read: boolean; | ||
} | ||
export interface SavedArticlesResponse { | ||
version: number; | ||
articles: SavedArticle[]; | ||
} | ||
export function isSavedArticlesResponse( | ||
data: unknown, | ||
): data is SavedArticlesResponse { | ||
// TODO - validate this properly when we have the schema | ||
return isObject(data); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export const typeCheckObject = | ||
<T>(guard: (o: unknown) => o is T) => | ||
(object: unknown): T | undefined => { | ||
return guard(object) ? object : undefined; | ||
}; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wonder if it's worth looking at the AsyncLoader utility used by the AccountOverview? There seem to be two async loaders in use in this repo - useAsyncLoader hook and the AsyncLoader class compononent. I would check with the rest of the MMA team which component is preferred and see if we can align our work with that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah good shout, I think from our discussion yesterday we think hooks is the right approach?