Skip to content

Commit

Permalink
fix: improve app creation realtime management
Browse files Browse the repository at this point in the history
Introduce AppTileWrapper component
to handle installing state on realtime app installation
  • Loading branch information
acezard committed Apr 27, 2023
1 parent e15aba1 commit f1af449
Show file tree
Hide file tree
Showing 10 changed files with 197 additions and 27 deletions.
86 changes: 66 additions & 20 deletions src/components/AppTile.jsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,78 @@
import React, { Component } from 'react'
import React, { useEffect, useState, useRef } from 'react'
import PropTypes from 'prop-types'
import { models } from 'cozy-client'

import { translate } from 'cozy-ui/transpiled/react/I18n'
import { models, useClient } from 'cozy-client'
import AppLinker from 'cozy-ui/transpiled/react/AppLinker'
import SquareAppIcon from 'cozy-ui/transpiled/react/SquareAppIcon'

import { fetchAppInfo } from 'queries'
import { useI18n } from 'cozy-ui/transpiled/react/I18n'

const { applications } = models

export class AppTile extends Component {
render() {
const { app, lang } = this.props
const displayName = applications.getAppDisplayName(app, lang)
const appHref = app.links && app.links.related
return (
<AppLinker href={appHref} app={app}>
{({ onClick, href, iconRef }) => (
<a onClick={onClick} href={href} className="scale-hover">
<SquareAppIcon app={app} name={displayName} iconRef={iconRef} />
</a>
)}
</AppLinker>
)
}
// AppTileWrapper is responsible for fetching the app's information
// if the app state changes from 'installing' to 'ready'
const AppTileWrapper = ({ app, lang }) => {
const client = useClient()
const [appInfo, setAppInfo] = useState(app.state === 'ready' ? app : null)
const prevState = useRef(app.state)
const { t } = useI18n()

// If app state changes from 'installing' to 'ready', fetch app info
useEffect(() => {
const loadAppInfo = async () => {
const fetchedAppInfo = await fetchAppInfo(app._id, client)
setAppInfo(fetchedAppInfo)
}

if (prevState.current === 'installing' && app.state === 'ready') {
prevState.current = app.state
loadAppInfo()
}
}, [app, app.state, client])

// Show loading icon if app information is not available, otherwise render AppTile
return !appInfo ? (
<SquareAppIcon
app={{
name: t('apps.installing'),
slug: '',
type: 'app'
}}
name=""
variant="loading"
IconContent={<div />}
/>
) : (
<AppTile app={appInfo} lang={lang} />
)
}

// AppTile is responsible for rendering the app icon with a link to the app
const AppTile = ({ app, lang }) => {
const displayName = applications.getAppDisplayName(app, lang)
const appHref = app?.links?.related || app.links?.related
const isErrored = app.state === 'errored'

return (
<AppLinker href={appHref} app={app}>
{({ onClick, href, iconRef }) => (
<a onClick={onClick} href={href} className="scale-hover">
<SquareAppIcon
app={app}
name={displayName}
iconRef={iconRef}
variant={isErrored ? 'error' : undefined}
/>
</a>
)}
</AppLinker>
)
}

AppTile.propTypes = {
app: PropTypes.object.isRequired,
t: PropTypes.func.isRequired
lang: PropTypes.string.isRequired
}

export default translate()(AppTile)
export default AppTileWrapper
109 changes: 109 additions & 0 deletions src/components/AppTile.spec.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import AppLike from 'test/AppLike'
import AppTileWrapper from './AppTile'
import MuiCozyTheme from 'cozy-ui/transpiled/react/MuiCozyTheme'
import React from 'react'
import { act } from 'react-dom/test-utils'
import { render, waitFor, screen } from '@testing-library/react'
import I18n from 'cozy-ui/transpiled/react/I18n'
import enLocale from 'locales/en.json'

const mockAppReady = {
_id: 'mock-app-id',
slug: 'mock-app-slug',
state: 'ready',
name: 'mock-app-name',
name_prefix: 'mock-app-name-prefix',
links: {
related: 'https://mock-app.cozy.tools',
icon: 'https://mock-app.cozy.tools/icon.png'
}
}

const mockClient = {
query: jest.fn().mockResolvedValue({
data: { ...mockAppReady }
})
}

const mockAppInstalling = {
_id: 'mock-app-id',
name: 'mock-app-name',
slug: 'mock-app-slug',
state: 'installing'
}

describe('<AppTile />', () => {
it('renders loading icon when app is in installing state', () => {
const { getByText } = render(
<AppLike>
<MuiCozyTheme>
<I18n dictRequire={() => enLocale} lang="en">
<AppTileWrapper app={mockAppInstalling} lang="en" />
</I18n>
</MuiCozyTheme>
</AppLike>
)

expect(getByText('Installing…')).toBeInTheDocument()
})

it('renders AppTile when app is in ready state', async () => {
const { queryByText } = render(
<AppLike client={mockClient}>
<MuiCozyTheme>
<AppTileWrapper app={mockAppReady} lang="en" />
</MuiCozyTheme>
</AppLike>
)

expect(queryByText('Chargement')).not.toBeInTheDocument()
})

it('updates app state from installing to ready and fetches app info', async () => {
const { rerender } = render(
<AppLike client={mockClient}>
<MuiCozyTheme>
<AppTileWrapper app={mockAppInstalling} lang="en" />
</MuiCozyTheme>
</AppLike>
)

await act(async () => {
rerender(
<AppLike client={mockClient}>
<MuiCozyTheme>
<AppTileWrapper app={mockAppReady} lang="en" />
</MuiCozyTheme>
</AppLike>
)
})

const appReadyElement = await screen.findByText(
`${mockAppReady.name_prefix} ${mockAppReady.name}`
)
expect(appReadyElement).toBeInTheDocument()
})

it('does not update app state from installing to ready if app state is not ready', async () => {
const { rerender } = render(
<AppLike client={mockClient}>
<MuiCozyTheme>
<AppTileWrapper app={mockAppInstalling} lang="en" />
</MuiCozyTheme>
</AppLike>
)

await waitFor(() => {
rerender(
<AppLike client={mockClient}>
<MuiCozyTheme>
<AppTileWrapper app={mockAppInstalling} lang="en" />
</MuiCozyTheme>
</AppLike>
)
})

const appReadyElement = await screen.findByText('Installing…')
expect(appReadyElement).toBeInTheDocument()
})
})
3 changes: 2 additions & 1 deletion src/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,8 @@
}
},
"apps": {
"title": "Meine Apps"
"title": "Meine Apps",
"installing": "Installieren…"
},
"connection": {
"CTA": {
Expand Down
3 changes: 2 additions & 1 deletion src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@
}
},
"apps": {
"title": "My apps"
"title": "My apps",
"installing": "Installing…"
},
"connection": {
"CTA": {
Expand Down
3 changes: 2 additions & 1 deletion src/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,8 @@
}
},
"apps": {
"title": "Mis apps"
"title": "Mis apps",
"installing": "Instalando…"
},
"connection": {
"CTA": {
Expand Down
3 changes: 2 additions & 1 deletion src/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@
}
},
"apps": {
"title": "Mes applications"
"title": "Mes applications",
"installing": "Installation…"
},
"connection": {
"CTA": {
Expand Down
3 changes: 2 additions & 1 deletion src/locales/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,8 @@
}
},
"apps": {
"title": "My apps"
"title": "My apps",
"installing": "Installazione…"
},
"connection": {
"CTA": {
Expand Down
3 changes: 2 additions & 1 deletion src/locales/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,8 @@
}
},
"apps": {
"title": "マイ アプリ"
"title": "マイ アプリ",
"installing": "インストール中…"
},
"connection": {
"CTA": {
Expand Down
3 changes: 2 additions & 1 deletion src/locales/nl_NL.json
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@
}
},
"apps": {
"title": "Mijn apps"
"title": "Mijn apps",
"installing": "Installeren…"
},
"connection": {
"CTA": {
Expand Down
8 changes: 8 additions & 0 deletions src/queries.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,11 @@ export const mkHomeCustomShorcutsConn = foldersId => {
fetchPolicy: defaultFetchPolicy
}
}

export const fetchAppInfo = async (appId, client) => {
const appQuery = Q('io.cozy.apps').getById(appId)
const { data } = await client.query(appQuery, {
as: `io.cozy.apps/${appId}`
})
return data
}

0 comments on commit f1af449

Please sign in to comment.