Repository navigation
fix(client): show a loading skeleton instead of the login form while resuming a session #41945
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
Merged
Merged
Changes from 1 commit
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
898b909
fix(client): show a loading skeleton instead of the login form while …
rodrigok 51abe0c
fix(client): fall through to the login form when the connection gives up
rodrigok b783071
fix(client): seed the unreachable latch from the current connection s…
rodrigok 938b345
fix(client): stop treating an idle connection as one that gave up
rodrigok 2f1cdac
refactor(client): make stored-item reads observable
tassoevan 04a5462
fix(client): show the login form when a session ends with its token s…
tassoevan f0c3c98
fix(client): notice a stored item another tab changed
tassoevan 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
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or 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 @@ | ||
| --- | ||
| '@rocket.chat/meteor': patch | ||
| --- | ||
|
|
||
| Shows the loading skeleton instead of flashing the login form while a stored session is being resumed |
84 changes: 84 additions & 0 deletions
84
apps/meteor/client/views/root/MainLayout/AuthenticationCheck.spec.tsx
This file contains hidden or 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,84 @@ | ||
| import { mockAppRoot } from '@rocket.chat/mock-providers'; | ||
| import type { AuthenticationContextValue, SessionContextValue } from '@rocket.chat/ui-contexts'; | ||
| import { AuthenticationContext, SessionContext } from '@rocket.chat/ui-contexts'; | ||
| import { render, screen } from '@testing-library/react'; | ||
| import type { ReactNode } from 'react'; | ||
|
|
||
| import AuthenticationCheck from './AuthenticationCheck'; | ||
| import { STORAGE_KEYS } from '../../../lib/sdk/storage'; | ||
|
|
||
| // The chain below this gate reaches for a great deal of app; what this spec is about is which of the three | ||
| // outcomes the gate picks, so stand the rest of it down. | ||
| jest.mock('./LoggedInArea', () => ({ __esModule: true, default: ({ children }: { children: ReactNode }) => <>{children}</> })); | ||
| jest.mock('./UsernameCheck', () => ({ __esModule: true, default: ({ children }: { children: ReactNode }) => <>{children}</> })); | ||
| jest.mock('./LoginPage', () => ({ __esModule: true, default: () => <div>login-page</div> })); | ||
| jest.mock('../../home/HomeSkeleton', () => ({ __esModule: true, default: () => <div>home-skeleton</div> })); | ||
|
|
||
| const renderGate = (root: ReturnType<typeof mockAppRoot>) => | ||
| render(<AuthenticationCheck>conference</AuthenticationCheck>, { wrapper: root.build() }); | ||
|
|
||
| afterEach(() => { | ||
| localStorage.clear(); | ||
| }); | ||
|
|
||
| it('renders the route for a user who is logged in', () => { | ||
| renderGate(mockAppRoot().withJohnDoe()); | ||
|
|
||
| expect(screen.getByText('conference')).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| it('shows the login page to a visitor with no session', () => { | ||
| renderGate(mockAppRoot().withAnonymous()); | ||
|
|
||
| expect(screen.getByText('login-page')).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| // The bug this guards: a window that opens with a session already stored — a call popout, or any plain reload — | ||
| // has no user until the login is resumed from that token, and showed a login form in the meantime. | ||
| describe('while the session is being resumed', () => { | ||
| it('shows the skeleton instead of the login page when a stored token is about to be used', () => { | ||
| localStorage.setItem(STORAGE_KEYS.LOGIN_TOKEN, 'a-stored-token'); | ||
|
|
||
| renderGate(mockAppRoot().withAnonymous()); | ||
|
|
||
| expect(screen.queryByText('login-page')).not.toBeInTheDocument(); | ||
| expect(screen.getByText('home-skeleton')).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| // Someone who was logged out, or whose session the server rejected, must reach the form — otherwise a stale | ||
| // token in storage would leave them staring at a skeleton forever. The mocked app root has no session | ||
| // support, so this one supplies the context itself. | ||
| it('shows the login page when a login is being forced', () => { | ||
| localStorage.setItem(STORAGE_KEYS.LOGIN_TOKEN, 'a-stale-token'); | ||
|
|
||
| const forcingLogin = { | ||
| query: () => [() => () => undefined, () => true] as ReturnType<SessionContextValue['query']>, | ||
| dispatch: () => undefined, | ||
| }; | ||
|
|
||
| renderGate( | ||
| mockAppRoot() | ||
| .withAnonymous() | ||
| .wrap((children) => <SessionContext.Provider value={forcingLogin}>{children}</SessionContext.Provider>), | ||
| ); | ||
|
|
||
| expect(screen.getByText('login-page')).toBeInTheDocument(); | ||
| }); | ||
| }); | ||
|
|
||
| // The regression that came with the guard above, and the reason it asks only about the stored token: `isLoggingIn` | ||
| // is true of *any* login in flight, a person typing their password at the form included. Swapping the form for a | ||
| // skeleton mid-attempt lost the rejection — the form came back blank, with neither field marked invalid — and | ||
| // iframe login never appeared at all, since the flow that fetches its URL runs from inside `LoginPage`. | ||
| it('keeps the login page up while someone is logging in at it', () => { | ||
| const loggingIn = { isLoggingIn: true } as AuthenticationContextValue; | ||
|
|
||
| renderGate( | ||
| mockAppRoot() | ||
| .withAnonymous() | ||
| .wrap((children) => <AuthenticationContext.Provider value={loggingIn}>{children}</AuthenticationContext.Provider>), | ||
| ); | ||
|
|
||
| expect(screen.getByText('login-page')).toBeInTheDocument(); | ||
| expect(screen.queryByText('home-skeleton')).not.toBeInTheDocument(); | ||
| }); | ||
This file contains hidden or 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
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.
Uh oh!
There was an error while loading. Please reload this page.