Files
windmill/frontend/src/lib/nonMemberWorkspaces.test.ts
T
Ruben FiszelandClaude Opus 5 3cef678b5f fix: show the fork banner to a superadmin who is not a workspace member (#10465)
* fix: show the fork banner to a superadmin who is not a workspace member

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: scope the non-member workspace cache to the current workspace

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: make the non-member workspace cache own exactly one workspace

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: drop the non-member workspace cache when no workspace is open

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 19:13:12 +02:00

71 lines
2.5 KiB
TypeScript

import { describe, expect, it, beforeEach } from 'vitest'
import { get } from 'svelte/store'
import {
clearNonMemberWorkspaces,
nonMemberWorkspaces,
setNonMemberWorkspaces,
superadmin,
userWorkspaces,
usersWorkspaceStore
} from './stores'
import type { Workspace } from './gen'
function workspace(id: string, extra: Partial<Workspace> = {}): Workspace {
return { id, name: id, owner: 'admin@windmill.dev', ...extra } as Workspace
}
beforeEach(() => {
usersWorkspaceStore.set(undefined)
superadmin.set(undefined)
nonMemberWorkspaces.set(undefined)
})
describe('nonMemberWorkspaces', () => {
it('merges the resolved workspaces into userWorkspaces with their fork lineage', () => {
setNonMemberWorkspaces('dev', [
workspace('dev', { parent_workspace_id: 'prod', is_dev_workspace: true }),
workspace('prod')
])
expect(get(userWorkspaces)).toMatchObject([
{ id: 'dev', parent_workspace_id: 'prod', is_dev_workspace: true },
{ id: 'prod' }
])
})
it('never offers an archived workspace, and still marks it resolved', () => {
setNonMemberWorkspaces('archived-fork', [workspace('archived-fork', { deleted: true })])
expect(get(userWorkspaces)).toEqual([])
// Without this the layout re-fetches the archived workspace on every effect pass.
expect(get(nonMemberWorkspaces)?.forWorkspace).toBe('archived-fork')
})
it('replaces the previous workspace set rather than accumulating across switches', () => {
setNonMemberWorkspaces('fork-a', [workspace('fork-a')])
setNonMemberWorkspaces('fork-b', [workspace('fork-b')])
expect(get(userWorkspaces).map((w) => w.id)).toEqual(['fork-b'])
})
it('does not duplicate a workspace the user is a member of', () => {
usersWorkspaceStore.set({
email: 'admin@windmill.dev',
workspaces: [{ id: 'dev', name: 'dev', username: 'admin', color: '', disabled: false }]
} as any)
setNonMemberWorkspaces('dev', [workspace('dev')])
expect(get(userWorkspaces).map((w) => w.id)).toEqual(['dev'])
})
// The layout effect that fills this store also reads it, and its member path — every
// ordinary user, every pass — clears it. So the empty state must be one Svelte won't
// re-notify for: an object value always notifies, however equal, and the effect would
// then invalidate itself forever.
it('does not notify when clearing an already empty store', () => {
let notifications = 0
const unsubscribe = userWorkspaces.subscribe(() => notifications++)
notifications = 0
clearNonMemberWorkspaces()
clearNonMemberWorkspaces()
expect(notifications).toBe(0)
unsubscribe()
})
})