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')
+ })
+ })
+ })
})