mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-05 16:03:47 +00:00
* 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>
71 lines
2.5 KiB
TypeScript
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()
|
|
})
|
|
})
|