diff --git a/services/web/frontend/js/features/ide-react/components/toolbar/offline-indicator.tsx b/services/web/frontend/js/features/ide-react/components/toolbar/offline-indicator.tsx index f1ccc118d6..f4e418f05d 100644 --- a/services/web/frontend/js/features/ide-react/components/toolbar/offline-indicator.tsx +++ b/services/web/frontend/js/features/ide-react/components/toolbar/offline-indicator.tsx @@ -1,8 +1,6 @@ import { useTranslation } from 'react-i18next' import MaterialIcon from '@/shared/components/material-icon' import OLTooltip from '@/shared/components/ol/ol-tooltip' -import { useUnsavedDocsContext } from '@/features/ide-react/context/unsaved-docs-context' -import { useFeatureFlag } from '@/shared/context/split-test-context' export function OfflineIndicatorContent() { const { t } = useTranslation() @@ -20,22 +18,12 @@ export function OfflineIndicatorContent() { ) } -export default function OfflineIndicator() { +export default function OfflineIndicator({ + isOffline, +}: { + isOffline: boolean +}) { const { t } = useTranslation() - const { unsavedDocs } = useUnsavedDocsContext() - const improvedFlakyConnections = useFeatureFlag( - 'intermittent-connection-improvements' - ) - - if (!improvedFlakyConnections) { - return null - } - - // TODO: use shared hook for this when merged - const MAX_UNSAVED_ALERT_SECONDS = 15 - const isOffline = [...unsavedDocs.values()].some( - seconds => seconds >= MAX_UNSAVED_ALERT_SECONDS - ) return ( <> {isOffline && } diff --git a/services/web/frontend/js/features/ide-react/components/toolbar/toolbar.tsx b/services/web/frontend/js/features/ide-react/components/toolbar/toolbar.tsx index 0eac92b5ec..ab5b717f39 100644 --- a/services/web/frontend/js/features/ide-react/components/toolbar/toolbar.tsx +++ b/services/web/frontend/js/features/ide-react/components/toolbar/toolbar.tsx @@ -16,6 +16,7 @@ import UpgradeButton from './upgrade-button' import getMeta from '@/utils/meta' import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context' import { useFeatureFlag } from '@/shared/context/split-test-context' +import useIsNetworkStalled from '@/features/ide-react/hooks/use-is-network-stalled' import OLIconButton from '@/shared/components/ol/ol-icon-button' import OLTooltip from '@/shared/components/ol/ol-tooltip' import SplitTestBadge from '@/shared/components/split-test-badge' @@ -33,7 +34,12 @@ export const Toolbar = () => { const upgradeButtonRelocation = useFeatureFlag( 'editor-upgrade-button-relocation' ) + const improvedFlakyConnections = useFeatureFlag( + 'intermittent-connection-improvements' + ) const { t } = useTranslation() + + const isOfflineDueToNetworkStall = useIsNetworkStalled() const shouldDisplaySubmitButton = (permissionsLevel === 'owner' || permissionsLevel === 'readAndWrite') && SubmitProjectButton @@ -130,9 +136,11 @@ export const Toolbar = () => {
- + {improvedFlakyConnections && ( + + )}
- + {!isOfflineDueToNetworkStall && } {!isRestrictedTokenMember && } {shouldDisplaySubmitButton && cobranding && ( diff --git a/services/web/test/frontend/features/ide-react/components/toolbar/offline-indicator.spec.tsx b/services/web/test/frontend/features/ide-react/components/toolbar/offline-indicator.spec.tsx index e2a4af90af..5f31d65e78 100644 --- a/services/web/test/frontend/features/ide-react/components/toolbar/offline-indicator.spec.tsx +++ b/services/web/test/frontend/features/ide-react/components/toolbar/offline-indicator.spec.tsx @@ -1,79 +1,19 @@ import OfflineIndicator from '@/features/ide-react/components/toolbar/offline-indicator' -import { UnsavedDocsContext } from '@/features/ide-react/context/unsaved-docs-context' -import { IdeReactContext } from '@/features/ide-react/context/ide-react-context' -import { IdeEventEmitter } from '@/features/ide-react/create-ide-event-emitter' -import { SplitTestProvider } from '@/shared/context/split-test-context' - -const defaultIdeReactContextValue = { - projectId: 'test-project', - eventEmitter: new IdeEventEmitter(), - startedFreeTrial: false, - setStartedFreeTrial: () => {}, - reportError: () => {}, - projectJoined: true, - permissionsLevel: 'owner' as const, - setPermissionsLevel: () => {}, - outOfSync: false, - setOutOfSync: () => {}, -} - -const defaultUnsavedDocsContextValue = { - unsavedDocs: new Map(), - isLocked: false, - isSavingStalled: false, -} - -const mount = ({ - unsavedDocs = new Map(), -}: { - unsavedDocs?: Map -} = {}) => { - cy.mount( - - - - - - - - ) -} - -const enableFlag = () => { - cy.window().then(win => { - win.metaAttributesCache.set('ol-splitTestVariants', { - 'intermittent-connection-improvements': 'enabled', - }) - }) -} - -const unsavedDoc = () => new Map([['doc-1', 20]]) describe('', function () { - it('is hidden when the feature flag is disabled', function () { - mount({ unsavedDocs: unsavedDoc() }) - cy.findByText('You’re offline').should('not.exist') - cy.findByRole('status').should('not.exist') - }) - - it('is hidden when the flag is enabled but there are no unsaved docs', function () { - enableFlag() - mount() + it('renders an empty live region when online', function () { + cy.mount() cy.findByText('You’re offline').should('not.exist') cy.findByRole('status').should('be.empty') }) - it('is shown when the flag is enabled and there are unsaved docs', function () { - enableFlag() - mount({ unsavedDocs: unsavedDoc() }) + it('shows the offline indicator when offline', function () { + cy.mount() cy.findAllByText('You’re offline').first().should('be.visible') }) it('announces the offline status to screen readers via a live region', function () { - enableFlag() - mount({ unsavedDocs: unsavedDoc() }) + cy.mount() cy.findByRole('status').within(() => { cy.findByText('You’re offline').should('exist') cy.findByText( diff --git a/services/web/test/frontend/features/ide-react/components/toolbar/online-users.spec.tsx b/services/web/test/frontend/features/ide-react/components/toolbar/online-users.spec.tsx new file mode 100644 index 0000000000..f25ec43b1f --- /dev/null +++ b/services/web/test/frontend/features/ide-react/components/toolbar/online-users.spec.tsx @@ -0,0 +1,47 @@ +import { OnlineUsers } from '@/features/ide-react/components/toolbar/online-users' +import { EditorManagerContext } from '@/features/ide-react/context/editor-manager-context' +import { OnlineUsersContext } from '@/features/ide-react/context/online-users-context' +import type { OnlineUser } from '@/features/ide-react/context/online-users-context' + +const onlineUser: OnlineUser = { + id: 'client-1', + user_id: 'user-1', + email: 'alice@example.com', + name: 'alice', + initial: 'a', +} + +const mount = ({ + onlineUsersArray = [onlineUser], +}: { + onlineUsersArray?: OnlineUser[] +} = {}) => { + const mockOpenDoc = cy.stub().as('openDoc') + cy.mount( + + + + + + ) +} + +describe('', function () { + it('renders nothing when there are no online users', function () { + mount({ onlineUsersArray: [] }) + cy.get('.ide-redesign-online-users').should('not.exist') + }) + + it('renders the widget when there are online users', function () { + mount() + cy.get('.ide-redesign-online-users').should('exist') + cy.findByText('a').should('be.visible') + }) +}) diff --git a/services/web/test/frontend/features/ide-react/unit/toolbar.spec.tsx b/services/web/test/frontend/features/ide-react/unit/toolbar.spec.tsx index 502e06a377..65bc4aaaa8 100644 --- a/services/web/test/frontend/features/ide-react/unit/toolbar.spec.tsx +++ b/services/web/test/frontend/features/ide-react/unit/toolbar.spec.tsx @@ -4,6 +4,11 @@ import { makeEditorProvider, } from '../../../helpers/editor-providers' import { Cobranding } from '@ol-types/cobranding' +import { + OnlineUsersContext, + type OnlineUser, +} from '@/features/ide-react/context/online-users-context' +import { UnsavedDocsContext } from '@/features/ide-react/context/unsaved-docs-context' import partnerLogoUrl from './cobranding-logo.png' describe('', function () { @@ -245,4 +250,66 @@ describe('', function () { // TODO: Test all the dynamic items }) + + describe('offline gating', function () { + const onlineUser: OnlineUser = { + id: 'client-1', + user_id: 'user-1', + email: 'alice@example.com', + name: 'alice', + initial: 'a', + } + + const mountToolbar = ({ offline }: { offline: boolean }) => { + const unsavedDocs = offline ? new Map([['doc-1', 20]]) : new Map() + cy.mount( + + + + + + + + ) + } + + describe('with intermittent-connection-improvements enabled', function () { + beforeEach(function () { + cy.window().then(win => { + win.metaAttributesCache.set('ol-splitTestVariants', { + 'intermittent-connection-improvements': 'enabled', + }) + }) + }) + + it('shows online users and no offline indicator when online', function () { + mountToolbar({ offline: false }) + cy.get('.ide-redesign-online-users').should('exist') + cy.findByText('You’re offline').should('not.exist') + }) + + it('hides online users and shows the offline indicator when offline', function () { + mountToolbar({ offline: true }) + cy.get('.ide-redesign-online-users').should('not.exist') + cy.findAllByText('You’re offline').first().should('be.visible') + }) + }) + + describe('with intermittent-connection-improvements disabled', function () { + it('always shows online users and never the offline indicator', function () { + mountToolbar({ offline: true }) + cy.get('.ide-redesign-online-users').should('exist') + cy.findByText('You’re offline').should('not.exist') + }) + }) + }) })