Persist and restore selected project and environment across page reloads

- Add refreshOrganization/refreshProject/refreshEnvironment to context store — update value and localStorage without clearing child selections
- OrgSelector: use refreshOrganization when same org is already stored, preventing the chain that was clearing project and environment
- ProjectSelector: watch org ID (not object ref) so refresh doesn't retrigger; add autoSelectProject to restore stored project or auto-select single project
- EnvironmentTabBar: watch project ID; skip env-clear on initial mount; add autoSelectEnvironment to restore stored env or fall back to first
- Fix test fixtures missing isPrimary field; update EnvironmentTabBar tests to match v-select implementation; add tests for restore behavior
This commit is contained in:
2026-06-11 11:26:19 -07:00
parent 1fddec7281
commit c57a13b350
22 changed files with 251 additions and 71 deletions

View File

@@ -23,12 +23,12 @@ const mockLogs: AuditLogResponse[] = [
{
id: 1, resourceType: 'Feature', resourceId: '42', action: 'Created',
changes: null, organizationId: 1, projectId: 10, environmentId: null,
actorUserId: 5, createdAt: '2026-01-10T10:00:00Z',
actorUserId: 5, actorUserName: 'Alice', createdAt: '2026-01-10T10:00:00Z',
},
{
id: 2, resourceType: 'FeatureState', resourceId: '99', action: 'Updated',
changes: '{"enabled":true}', organizationId: 1, projectId: 10, environmentId: 100,
actorUserId: 5, createdAt: '2026-01-11T11:00:00Z',
actorUserId: 5, actorUserName: 'Alice', createdAt: '2026-01-11T11:00:00Z',
},
];
@@ -58,7 +58,7 @@ describe('AuditLogsView', () => {
});
const contextStore = useContextStore();
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '' });
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '', isPrimary: false });
contextStore.setProject(mockProject);
mountView();
@@ -76,7 +76,7 @@ describe('AuditLogsView', () => {
});
const contextStore = useContextStore();
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '' });
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '', isPrimary: false });
contextStore.setProject(mockProject);
const wrapper = mountView();
@@ -95,7 +95,7 @@ describe('AuditLogsView', () => {
});
const contextStore = useContextStore();
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '' });
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '', isPrimary: false });
contextStore.setProject(mockProject);
const wrapper = mountView();
@@ -120,7 +120,7 @@ describe('AuditLogsView', () => {
});
const contextStore = useContextStore();
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '' });
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '', isPrimary: false });
contextStore.setProject(mockProject);
const wrapper = mountView();
@@ -145,7 +145,7 @@ describe('AuditLogsView', () => {
});
const contextStore = useContextStore();
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '' });
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '', isPrimary: false });
contextStore.setProject(mockProject);
const wrapper = mountView();
@@ -170,7 +170,7 @@ describe('AuditLogsView', () => {
});
const contextStore = useContextStore();
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '' });
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '', isPrimary: false });
contextStore.setProject(mockProject);
const wrapper = mountView();
@@ -195,7 +195,7 @@ describe('AuditLogsView', () => {
});
const contextStore = useContextStore();
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '' });
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '', isPrimary: false });
contextStore.setProject(mockProject);
const wrapper = mountView();

View File

@@ -58,7 +58,7 @@ describe('FeaturesView', () => {
jest.mocked(featureStatesApi.listFeatureStates).mockResolvedValue(mockStates);
const contextStore = useContextStore();
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '' });
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '', isPrimary: false });
contextStore.setProject(mockProject);
contextStore.setEnvironment(mockEnv);
@@ -74,7 +74,7 @@ describe('FeaturesView', () => {
jest.mocked(featureStatesApi.listFeatureStates).mockResolvedValue(mockStates);
const contextStore = useContextStore();
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '' });
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '', isPrimary: false });
contextStore.setProject(mockProject);
const wrapper = mountView();
@@ -92,7 +92,7 @@ describe('FeaturesView', () => {
jest.mocked(featureStatesApi.patchFeatureState).mockResolvedValue({ ...mockStates[0], enabled: true });
const contextStore = useContextStore();
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '' });
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '', isPrimary: false });
contextStore.setProject(mockProject);
contextStore.setEnvironment(mockEnv);
@@ -115,7 +115,7 @@ describe('FeaturesView', () => {
jest.mocked(featureStatesApi.listFeatureStates).mockResolvedValue([]);
const contextStore = useContextStore();
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '' });
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '', isPrimary: false });
contextStore.setProject(mockProject);
const wrapper = mountView();

View File

@@ -57,7 +57,7 @@ describe('IdentitiesView', () => {
});
const contextStore = useContextStore();
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '' });
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '', isPrimary: false });
contextStore.setProject(mockProject);
contextStore.setEnvironment(mockEnv);
@@ -73,7 +73,7 @@ describe('IdentitiesView', () => {
});
const contextStore = useContextStore();
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '' });
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '', isPrimary: false });
contextStore.setProject(mockProject);
contextStore.setEnvironment(mockEnv);
@@ -91,7 +91,7 @@ describe('IdentitiesView', () => {
});
const contextStore = useContextStore();
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '' });
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '', isPrimary: false });
contextStore.setProject(mockProject);
contextStore.setEnvironment(mockEnv);
@@ -115,7 +115,7 @@ describe('IdentitiesView', () => {
});
const contextStore = useContextStore();
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '' });
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '', isPrimary: false });
contextStore.setProject(mockProject);
contextStore.setEnvironment(mockEnv);
@@ -137,7 +137,7 @@ describe('IdentitiesView', () => {
});
const contextStore = useContextStore();
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '' });
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '', isPrimary: false });
contextStore.setProject(mockProject);
contextStore.setEnvironment(mockEnv);
@@ -163,7 +163,7 @@ describe('IdentitiesView', () => {
});
const contextStore = useContextStore();
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '' });
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '', isPrimary: false });
contextStore.setProject(mockProject);
contextStore.setEnvironment(mockEnv);

View File

@@ -44,7 +44,7 @@ describe('ProfileView', () => {
it('ThenOrgNameIsDisplayedWhenSelected', () => {
const { wrapper } = mountView();
const contextStore = useContextStore();
contextStore.setOrganization({ id: 1, name: 'Acme Corp', createdAt: '' });
contextStore.setOrganization({ id: 1, name: 'Acme Corp', createdAt: '', isPrimary: false });
expect(wrapper.find('[data-testid="profile-org-name"]').exists()).toBe(false); // org name shown in v-if block
});

View File

@@ -61,7 +61,7 @@ describe('SegmentsView', () => {
jest.mocked(segmentsApi.listSegments).mockResolvedValue(mockSegments);
const contextStore = useContextStore();
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '' });
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '', isPrimary: false });
contextStore.setProject(mockProject);
mountView();
@@ -74,7 +74,7 @@ describe('SegmentsView', () => {
jest.mocked(segmentsApi.listSegments).mockResolvedValue(mockSegments);
const contextStore = useContextStore();
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '' });
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '', isPrimary: false });
contextStore.setProject(mockProject);
const wrapper = mountView();
@@ -88,7 +88,7 @@ describe('SegmentsView', () => {
jest.mocked(segmentsApi.listSegments).mockResolvedValue(mockSegments);
const contextStore = useContextStore();
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '' });
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '', isPrimary: false });
contextStore.setProject(mockProject);
const wrapper = mountView();
@@ -104,7 +104,7 @@ describe('SegmentsView', () => {
jest.mocked(segmentsApi.listSegments).mockResolvedValue([]);
const contextStore = useContextStore();
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '' });
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '', isPrimary: false });
contextStore.setProject(mockProject);
const wrapper = mountView();
@@ -123,7 +123,7 @@ describe('SegmentsView', () => {
jest.mocked(segmentsApi.listSegments).mockResolvedValue(mockSegments);
const contextStore = useContextStore();
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '' });
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '', isPrimary: false });
contextStore.setProject(mockProject);
const wrapper = mountView();
@@ -143,7 +143,7 @@ describe('SegmentsView', () => {
jest.mocked(segmentsApi.deleteSegment).mockResolvedValue(undefined);
const contextStore = useContextStore();
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '' });
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '', isPrimary: false });
contextStore.setProject(mockProject);
const wrapper = mountView();
@@ -172,7 +172,7 @@ describe('SegmentsView', () => {
jest.mocked(segmentsApi.deleteSegment).mockResolvedValue(undefined);
const contextStore = useContextStore();
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '' });
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '', isPrimary: false });
contextStore.setProject(mockProject);
const wrapper = mountView();
@@ -196,7 +196,7 @@ describe('SegmentsView', () => {
jest.mocked(segmentsApi.listSegments).mockResolvedValue([]);
const contextStore = useContextStore();
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '' });
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '', isPrimary: false });
contextStore.setProject(mockProject);
const wrapper = mountView();
@@ -214,7 +214,7 @@ describe('SegmentsView', () => {
jest.mocked(segmentsApi.listSegments).mockResolvedValue([...mockSegments]);
const contextStore = useContextStore();
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '' });
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '', isPrimary: false });
contextStore.setProject(mockProject);
const wrapper = mountView();

View File

@@ -26,7 +26,7 @@ import * as environmentsApi from '@/api/environments';
import * as apiKeysApi from '@/api/apiKeys';
import * as webhooksApi from '@/api/webhooks';
const mockOrg: OrganizationResponse = { id: 1, name: 'Acme', createdAt: '2026-01-01T00:00:00Z' };
const mockOrg: OrganizationResponse = { id: 1, name: 'Acme', createdAt: '2026-01-01T00:00:00Z', isPrimary: false };
const mockProject: ProjectResponse = { id: 10, name: 'Website', organizationId: 1, hideDisabledFlags: false, createdAt: '2026-01-01T00:00:00Z' };
const mockEnvs: EnvironmentResponse[] = [
{ id: 100, name: 'Development', apiKey: 'env-dev', projectId: 10, createdAt: '2026-01-01T00:00:00Z' },