Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions __tests__/e2e/admin/tenant-selector/dashboard.e2e.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { expect, tenantSelectorTest as test } from '../../fixtures/tenant-selector.fixture'
import { AdminUrlUtil, CollectionSlugs } from '../../helpers'

/**
* Dashboard Tests
*
* Tests that the tenant selector is visible and functional on the admin dashboard.
* Previously, the selector was hidden on the dashboard because viewType was never
* set to 'dashboard'. Fixed by detecting dashboard from URL params instead.
*
* Related: https://github.com/NWACus/web/issues/691
*/

test.describe.configure({ timeout: 90000 })

test.describe('Tenant selector on dashboard', () => {
test('should be visible on the dashboard for super admin', async ({
loginAs,
isTenantSelectorVisible,
getTenantOptions,
}) => {
const page = await loginAs('superAdmin')
const url = new AdminUrlUtil('http://localhost:3000', CollectionSlugs.pages)

await page.goto(url.dashboard)
await page.waitForLoadState('networkidle')

const isVisible = await isTenantSelectorVisible(page)
expect(isVisible).toBe(true)

const options = await getTenantOptions(page)
expect(options.length).toBeGreaterThanOrEqual(4)

await page.context().close()
})

test('should be visible on the dashboard for multi-tenant admin', async ({
loginAs,
isTenantSelectorVisible,
getTenantOptions,
}) => {
const page = await loginAs('multiTenantAdmin')
const url = new AdminUrlUtil('http://localhost:3000', CollectionSlugs.pages)

await page.goto(url.dashboard)
await page.waitForLoadState('networkidle')

const isVisible = await isTenantSelectorVisible(page)
expect(isVisible).toBe(true)

const options = await getTenantOptions(page)
expect(options.length).toBe(2) // NWAC and SNFAC

await page.context().close()
})

test('should be hidden on dashboard for single-tenant users', async ({
loginAs,
isTenantSelectorVisible,
}) => {
const page = await loginAs('singleTenantAdmin')
const url = new AdminUrlUtil('http://localhost:3000', CollectionSlugs.pages)

await page.goto(url.dashboard)
await page.waitForLoadState('networkidle')

// Single-tenant users have only 1 option, so selector is hidden
const isVisible = await isTenantSelectorVisible(page)
expect(isVisible).toBe(false)

await page.context().close()
})
})
106 changes: 106 additions & 0 deletions __tests__/e2e/admin/tenant-selector/sync-on-save.e2e.spec.ts
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the renaming of dvac isn't being correctly cleaned up somehow.

I'm getting the following test failure when running e2e tests locally. This only happens when running right after a pnpm seed. If I run this test, get the failure, then run again, it succeeds. So run a fresh seed first.

pnpm seed
pnpm dev
pnpm test:e2e __tests__/e2e/admin/tenant-selector

Image

admin/tenant-selector/tenant-cookie-edge-cases.e2e.spec.ts:86

Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { openNav } from '../../fixtures/nav.fixture'
import { expect, tenantSelectorTest as test } from '../../fixtures/tenant-selector.fixture'
import { AdminUrlUtil, CollectionSlugs, saveDocAndAssert, waitForFormReady } from '../../helpers'

/**
* Sync on Save Tests
*
* Tests that the tenant selector dropdown refreshes automatically when a
* tenant is saved (created or updated), without requiring a page reload.
*
* The SyncTenantsOnSave component watches useDocumentInfo().lastUpdateTime
* and calls syncTenants() when it changes.
*/

const TEMP_TENANT_SLUG_CREATE = 'e2e-sync-create'
const TEMP_TENANT_NAME_CREATE = 'E2E Sync Create Center'

const TEMP_TENANT_SLUG_EDIT = 'e2e-sync-edit'
const TEMP_TENANT_NAME_EDIT = 'E2E Sync Edit Center'
const UPDATED_TENANT_NAME = 'E2E Sync Edit Renamed'

test.describe.configure({ timeout: 90000 })

test.describe('Tenant selector syncs on save', () => {
test('should show new tenant in selector after creation', async ({
loginAs,
getTenantOptions,
}) => {
const page = await loginAs('superAdmin')
const tenantsUrl = new AdminUrlUtil('http://localhost:3000', CollectionSlugs.tenants)

try {
// Navigate to create a new tenant
await page.goto(tenantsUrl.create)
await page.waitForLoadState('networkidle')
await waitForFormReady(page)

await page.locator('#field-name').fill(TEMP_TENANT_NAME_CREATE)
await page.locator('#field-slug').fill(TEMP_TENANT_SLUG_CREATE)
await saveDocAndAssert(page)

// Navigate to a tenant-scoped collection via client-side nav link (NOT page.goto)
await openNav(page)
await Promise.all([
page.waitForURL('**/admin/collections/pages'),
page.locator('nav a[href="/admin/collections/pages"]').click(),
])
await page.waitForLoadState('networkidle')

// The tenant selector should show the new tenant without a full page reload
const options = await getTenantOptions(page)
expect(options).toContain(TEMP_TENANT_NAME_CREATE)
} finally {
// Clean up: delete the temporary tenant
const response = await page.request.get(
`http://localhost:3000/api/tenants?where[slug][equals]=${TEMP_TENANT_SLUG_CREATE}&limit=1`,
)
const data = await response.json()
if (data.docs?.[0]) {
await page.request.delete(`http://localhost:3000/api/tenants/${data.docs[0].id}`)
}
await page.context().close()
}
})

test('should update tenant name in selector after editing', async ({
loginAs,
getTenantOptions,
}) => {
const page = await loginAs('superAdmin')
const tenantsUrl = new AdminUrlUtil('http://localhost:3000', CollectionSlugs.tenants)

// Create a temporary tenant via API
const createResponse = await page.request.post('http://localhost:3000/api/tenants', {
data: { name: TEMP_TENANT_NAME_EDIT, slug: TEMP_TENANT_SLUG_EDIT },
})
const { doc: tenant } = await createResponse.json()

try {
// Navigate to the tenant edit page
await page.goto(tenantsUrl.edit(tenant.id))
await page.waitForLoadState('networkidle')
await waitForFormReady(page)

// Rename the tenant
await page.locator('#field-name').fill(UPDATED_TENANT_NAME)
await saveDocAndAssert(page)

// Navigate to a tenant-scoped collection via client-side nav link (NOT page.goto)
await openNav(page)
await Promise.all([
page.waitForURL('**/admin/collections/pages'),
page.locator('nav a[href="/admin/collections/pages"]').click(),
])
await page.waitForLoadState('networkidle')

const options = await getTenantOptions(page)
expect(options).toContain(UPDATED_TENANT_NAME)
expect(options).not.toContain(TEMP_TENANT_NAME_EDIT)
} finally {
// Clean up: delete the temporary tenant
await page.request.delete(`http://localhost:3000/api/tenants/${tenant.id}`)
await page.context().close()
}
})
})
9 changes: 0 additions & 9 deletions docs/onboarding.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,6 @@ This outlines steps required when a new center (tenant) comes on board. This doc
<td>Manual</td>
<td>Troubleshoot: <br />In Vercel, go to the project → Storage → production Edge Config and edit file</td>
</tr>
<tr>
<td rowspan="2">New tenant option shown in <code>TenantSelectionProvider</code></td>
<td>Manual</td>
<td>Refresh page</td>
</tr>
<tr>
<td>Future automation</td>
<td>Use <code>syncTenants</code></td>
</tr>
<tr>
<td>Create documents for Global Collections <ul><li>Website Settings</><li>Navigation</li><li>Home Pages</li></ul></td>
<td>Manual</td>
Expand Down
2 changes: 2 additions & 0 deletions src/app/(payload)/admin/importMap.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions src/collections/Tenants/components/SyncTenantsOnSave.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use client'

import { useDocumentInfo } from '@payloadcms/ui'
import { useEffect, useRef } from 'react'

import { useTenantSelection } from '@/providers/TenantSelectionProvider/index.client'

/**
* Invisible component placed on the Tenants edit view that syncs the tenant
* selector dropdown whenever a tenant document is saved (created or updated).
*/
export const SyncTenantsOnSave = () => {
const { lastUpdateTime } = useDocumentInfo()
const { syncTenants } = useTenantSelection()
const prevUpdateTime = useRef(lastUpdateTime)

useEffect(() => {
if (lastUpdateTime !== prevUpdateTime.current) {
prevUpdateTime.current = lastUpdateTime
syncTenants()
}
}, [lastUpdateTime, syncTenants])

return null
}
7 changes: 7 additions & 0 deletions src/collections/Tenants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ export const Tenants: CollectionConfig = {
useAsTitle: 'name',
group: 'Permissions',
hidden: ({ user }) => hasReadOnlyAccess(user, 'tenants'),
components: {
edit: {
beforeDocumentControls: [
'@/collections/Tenants/components/SyncTenantsOnSave#SyncTenantsOnSave',
],
},
},
},
labels: {
plural: 'Avalanche Centers',
Expand Down