mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
This commit is contained in:
@@ -9,9 +9,10 @@
|
||||
* background and emits MemorySyncStageChanged events.
|
||||
*/
|
||||
import debug from 'debug';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { useT } from '../../lib/i18n/I18nContext';
|
||||
import { CoreStateContext } from '../../providers/coreStateContext';
|
||||
import {
|
||||
applyAllIn,
|
||||
type FreshnessLabel,
|
||||
@@ -122,6 +123,12 @@ export function MemorySourcesRegistry({
|
||||
pollIntervalMs = 5000,
|
||||
}: MemorySourcesRegistryProps) {
|
||||
const { t } = useT();
|
||||
// Read the core snapshot directly (not via the throwing `useCoreState`
|
||||
// hook) so this component still renders in unit tests that mount it
|
||||
// without a CoreStateProvider — there `ctx` is null and `isAuthenticated`
|
||||
// stays a stable `false`, so the load effect behaves exactly as before.
|
||||
const coreState = useContext(CoreStateContext);
|
||||
const isAuthenticated = coreState?.snapshot.auth.isAuthenticated ?? false;
|
||||
const [sources, setSources] = useState<MemorySourceEntry[]>([]);
|
||||
const [statuses, setStatuses] = useState<SourceStatus[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -282,9 +289,16 @@ export function MemorySourcesRegistry({
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Load on mount AND whenever the session transitions to authenticated.
|
||||
// After a page reload the registry can mount (e.g. via a persisted
|
||||
// `?tab=memory` deep link) *before* CoreStateProvider has restored the
|
||||
// session, so the initial fetch runs against a not-yet-ready core and
|
||||
// surfaces nothing. Re-running when `isAuthenticated` flips true picks up
|
||||
// sources immediately instead of waiting for the next 5s poll — which
|
||||
// under CI load was racing the E2E visibility timeout (#3449).
|
||||
useEffect(() => {
|
||||
void refresh();
|
||||
}, [refresh]);
|
||||
}, [refresh, isAuthenticated]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!pollIntervalMs) return undefined;
|
||||
|
||||
@@ -2,12 +2,16 @@
|
||||
* Unit tests for MemorySourcesRegistry — All In button, gear/settings panel,
|
||||
* per-kind field visibility, Save, and existing toggle behaviour.
|
||||
*/
|
||||
import { fireEvent, screen, waitFor, within } from '@testing-library/react';
|
||||
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import * as service from '../../../services/memorySourcesService';
|
||||
import { getCoreStateSnapshot } from '../../../lib/coreState/store';
|
||||
import { CoreStateContext, type CoreStateContextValue } from '../../../providers/coreStateContext';
|
||||
import type { MemorySourceEntry } from '../../../services/memorySourcesService';
|
||||
import { renderWithProviders } from '../../../test/test-utils';
|
||||
import { createTestStore, renderWithProviders } from '../../../test/test-utils';
|
||||
import {
|
||||
openhumanGetMemorySyncSettings,
|
||||
openhumanUpdateMemorySyncSettings,
|
||||
@@ -430,4 +434,63 @@ describe('MemorySourcesRegistry', () => {
|
||||
expect(onToast).toHaveBeenCalledWith(expect.objectContaining({ type: 'error' }));
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Refetch when the session becomes authenticated (#3449)
|
||||
//
|
||||
// After a page reload the registry can mount before CoreStateProvider has
|
||||
// restored the session. The first fetch then runs against a not-yet-ready
|
||||
// core; sources must reappear as soon as auth flips true, without waiting
|
||||
// for the next background poll.
|
||||
// -------------------------------------------------------------------------
|
||||
it('refetches sources when the session transitions to authenticated', async () => {
|
||||
mockedList.mockResolvedValue([makeSource({ label: 'Reloaded Repo' })]);
|
||||
mockedStatus.mockResolvedValue([]);
|
||||
|
||||
const coreState = (isAuthenticated: boolean): CoreStateContextValue =>
|
||||
({
|
||||
...getCoreStateSnapshot(),
|
||||
snapshot: {
|
||||
...getCoreStateSnapshot().snapshot,
|
||||
auth: {
|
||||
isAuthenticated,
|
||||
userId: isAuthenticated ? 'u1' : null,
|
||||
user: null,
|
||||
profileId: null,
|
||||
},
|
||||
},
|
||||
refresh: async () => {},
|
||||
refreshTeams: async () => {},
|
||||
refreshTeamMembers: async () => {},
|
||||
refreshTeamInvites: async () => {},
|
||||
setAnalyticsEnabled: async () => {},
|
||||
setMeetAutoOrchestratorHandoff: async () => {},
|
||||
setOnboardingCompletedFlag: async () => {},
|
||||
setEncryptionKey: async () => {},
|
||||
patchSnapshot: () => {},
|
||||
setOnboardingTasks: async () => {},
|
||||
storeSessionToken: async () => {},
|
||||
clearSession: async () => {},
|
||||
}) as CoreStateContextValue;
|
||||
|
||||
const store = createTestStore();
|
||||
const tree = (isAuthenticated: boolean) => (
|
||||
<Provider store={store}>
|
||||
<CoreStateContext.Provider value={coreState(isAuthenticated)}>
|
||||
<MemoryRouter>
|
||||
{/* pollIntervalMs=0 disables the background poll, so the only way
|
||||
the second fetch can fire is the auth transition. */}
|
||||
<MemorySourcesRegistry pollIntervalMs={0} />
|
||||
</MemoryRouter>
|
||||
</CoreStateContext.Provider>
|
||||
</Provider>
|
||||
);
|
||||
|
||||
const { rerender } = render(tree(false));
|
||||
await waitFor(() => expect(mockedList).toHaveBeenCalledTimes(1));
|
||||
|
||||
rerender(tree(true));
|
||||
await waitFor(() => expect(mockedList).toHaveBeenCalledTimes(2));
|
||||
expect(await screen.findByText('Reloaded Repo')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user