diff --git a/frontend/src/lib/components/header/header.svelte b/frontend/src/lib/components/header/header.svelte index 431ce4ba..6ddad320 100644 --- a/frontend/src/lib/components/header/header.svelte +++ b/frontend/src/lib/components/header/header.svelte @@ -28,7 +28,7 @@ >
diff --git a/frontend/src/lib/components/ui/tabs/tabs-content.svelte b/frontend/src/lib/components/ui/tabs/tabs-content.svelte index 07ab3308..abb2c164 100644 --- a/frontend/src/lib/components/ui/tabs/tabs-content.svelte +++ b/frontend/src/lib/components/ui/tabs/tabs-content.svelte @@ -12,6 +12,9 @@ diff --git a/frontend/src/lib/components/ui/tabs/tabs-list.svelte b/frontend/src/lib/components/ui/tabs/tabs-list.svelte index b6e29c2d..ecab982a 100644 --- a/frontend/src/lib/components/ui/tabs/tabs-list.svelte +++ b/frontend/src/lib/components/ui/tabs/tabs-list.svelte @@ -19,22 +19,105 @@ +> + {@render children?.()} + {#if variant === 'line'} + + {/if} + diff --git a/frontend/src/lib/components/ui/tabs/tabs-trigger.svelte b/frontend/src/lib/components/ui/tabs/tabs-trigger.svelte index caa55b44..42e763cf 100644 --- a/frontend/src/lib/components/ui/tabs/tabs-trigger.svelte +++ b/frontend/src/lib/components/ui/tabs/tabs-trigger.svelte @@ -16,7 +16,6 @@ "gap-2 rounded-full border border-transparent! px-3 py-1 text-sm font-medium group-data-vertical/tabs:rounded-2xl group-data-vertical/tabs:px-3 group-data-vertical/tabs:py-1.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*='size-'])]:size-4 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground/60 hover:text-foreground dark:text-muted-foreground dark:hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0", 'group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent', 'data-active:bg-background dark:data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 data-active:text-foreground', - 'after:bg-foreground after:absolute after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100', className )} {...restProps} diff --git a/frontend/src/lib/components/ui/tabs/tabs.svelte b/frontend/src/lib/components/ui/tabs/tabs.svelte index 5b4a44fa..9dae5d31 100644 --- a/frontend/src/lib/components/ui/tabs/tabs.svelte +++ b/frontend/src/lib/components/ui/tabs/tabs.svelte @@ -1,18 +1,36 @@
diff --git a/frontend/src/routes/settings/admin/application-configuration/+page.svelte b/frontend/src/routes/settings/admin/application-configuration/+page.svelte index e8a9df12..432768ef 100644 --- a/frontend/src/routes/settings/admin/application-configuration/+page.svelte +++ b/frontend/src/routes/settings/admin/application-configuration/+page.svelte @@ -1,18 +1,14 @@ diff --git a/tests/specs/api.spec.ts b/tests/specs/api.spec.ts index dd5a1aef..613e6dab 100644 --- a/tests/specs/api.spec.ts +++ b/tests/specs/api.spec.ts @@ -108,8 +108,8 @@ test('Grant a client user-delegated and client access to API permissions', async // Nextcloud has no API access granted by default await page.goto(`/settings/admin/oidc-clients/${oidcClients.nextcloud.id}`); - // Expand the API access card, then edit the Orders API row - await page.getByText('API access', { exact: true }).click(); + // Open the API access tab, then edit the Orders API row + await page.getByRole('tab', { name: 'API access' }).click(); await page .getByRole('row', { name: apis.orders.name }) .getByRole('button', { name: 'Edit' }) diff --git a/tests/specs/application-configuration.spec.ts b/tests/specs/application-configuration.spec.ts index 0a619b76..8bc80fce 100644 --- a/tests/specs/application-configuration.spec.ts +++ b/tests/specs/application-configuration.spec.ts @@ -30,14 +30,14 @@ test('Update general configuration', async ({ page }) => { test.describe('Update user creation configuration', () => { test.beforeEach(async ({ page }) => { - await page.getByRole('button', { name: 'Expand card' }).nth(1).click(); + await page.getByRole('tab', { name: 'User Creation' }).click(); }); test('should save sign up mode', async ({ page }) => { await page.getByRole('button', { name: 'Enable User Signups' }).click(); await page.getByRole('option', { name: 'Open Signup' }).click(); - await page.getByRole('button', { name: 'Save' }).nth(1).click(); + await page.getByRole('button', { name: 'Save' }).click(); await expect(page.locator('[data-type="success"]').last()).toHaveText( 'User creation settings updated successfully.' @@ -59,7 +59,7 @@ test.describe('Update user creation configuration', () => { await expect(designersOption).toBeChecked(); await page.keyboard.press('Escape'); - await page.getByRole('button', { name: 'Save' }).nth(1).click(); + await page.getByRole('button', { name: 'Save' }).click(); await expect(page.locator('[data-type="success"]').last()).toHaveText( 'User creation settings updated successfully.' @@ -81,7 +81,7 @@ test.describe('Update user creation configuration', () => { await page.getByPlaceholder('Key').nth(1).fill('another-claim'); await page.getByPlaceholder('Value').nth(1).fill('another-value'); - await page.getByRole('button', { name: 'Save' }).nth(1).click(); + await page.getByRole('button', { name: 'Save' }).click(); await expect(page.locator('[data-type="success"]').last()).toHaveText( 'User creation settings updated successfully.' @@ -97,7 +97,7 @@ test.describe('Update user creation configuration', () => { }); test('Update email configuration', async ({ page }) => { - await page.getByRole('button', { name: 'Expand card' }).nth(2).click(); + await page.getByRole('tab', { name: 'Email' }).click(); await page.getByLabel('SMTP Host').fill('smtp.gmail.com'); await page.getByLabel('SMTP Port').fill('587'); @@ -109,7 +109,7 @@ test('Update email configuration', async ({ page }) => { await page.getByLabel('Email Login Code from Admin').click(); await page.getByLabel('API Key Expiration').click(); - await page.getByRole('button', { name: 'Save' }).nth(1).click(); + await page.getByRole('button', { name: 'Save' }).click(); await expect(page.locator('[data-type="success"]')).toHaveText( 'Email configuration updated successfully' @@ -130,7 +130,7 @@ test('Update email configuration', async ({ page }) => { test.describe('Update application images', () => { test.beforeEach(async ({ page }) => { - await page.getByRole('button', { name: 'Expand card' }).nth(4).click(); + await page.getByRole('tab', { name: 'Images' }).click(); }); test('should upload images', async ({ page }) => { @@ -144,7 +144,7 @@ test.describe('Update application images', () => { .getByLabel('Default Profile Picture') .setInputFiles('resources/images/pingvin-share-logo.png'); await page.getByLabel('Background Image').setInputFiles('resources/images/clouds.jpg'); - await page.getByRole('button', { name: 'Save' }).last().click(); + await page.getByRole('button', { name: 'Save' }).click(); await expect(page.locator('[data-type="success"]')).toHaveText( 'Images updated successfully. It may take a few minutes to update.' @@ -171,7 +171,7 @@ test.describe('Update application images', () => { const emailLogoInput = page.getByLabel('Email Logo'); await emailLogoInput.setInputFiles('resources/images/cloud-logo.svg'); - await page.getByRole('button', { name: 'Save' }).last().click(); + await page.getByRole('button', { name: 'Save' }).click(); await expect(page.locator('[data-type="error"]')).toHaveText( 'File must be of type .png or .jpg/jpeg' diff --git a/tests/specs/ldap.spec.ts b/tests/specs/ldap.spec.ts index ed77da60..85074e54 100644 --- a/tests/specs/ldap.spec.ts +++ b/tests/specs/ldap.spec.ts @@ -12,7 +12,7 @@ test.describe('LDAP Integration', () => { test('LDAP configuration is working properly', async ({ page }) => { await page.goto('/settings/admin/application-configuration'); - await page.getByRole('button', { name: 'Expand card' }).nth(3).click(); + await page.getByRole('tab', { name: 'LDAP' }).click(); await expect(page.getByRole('button', { name: 'Disable', exact: true })).toBeVisible(); await expect(page.getByLabel('LDAP URL')).toHaveValue(/ldap:\/\/.*/); diff --git a/tests/specs/oidc-client-settings.spec.ts b/tests/specs/oidc-client-settings.spec.ts index ad41a640..577b7124 100644 --- a/tests/specs/oidc-client-settings.spec.ts +++ b/tests/specs/oidc-client-settings.spec.ts @@ -161,13 +161,14 @@ test('Filter OIDC clients by PAR requirement', async ({ page, request }) => { test('Update OIDC client allowed user groups', async ({ page }) => { await page.goto(`/settings/admin/oidc-clients/${oidcClients.nextcloud.id}`); + await page.getByRole('tab', { name: 'Allowed user groups' }).click(); await page.getByRole('button', { name: 'Restrict' }).click(); await page.getByRole('row', { name: userGroups.designers.name }).getByRole('checkbox').click(); await page.getByRole('row', { name: userGroups.developers.name }).getByRole('checkbox').click(); - await page.getByRole('button', { name: 'Save' }).nth(1).click(); + await page.getByRole('button', { name: 'Save' }).click(); await expect(page.getByText('Allowed user groups updated successfully')).toBeVisible(); diff --git a/tests/specs/oidc.spec.ts b/tests/specs/oidc.spec.ts index 76b55713..f556b331 100644 --- a/tests/specs/oidc.spec.ts +++ b/tests/specs/oidc.spec.ts @@ -1520,7 +1520,9 @@ test.describe('OIDC skip consent', () => { // Disabling it and saving must persist across a reload await toggle.click(); + await expect(toggle).not.toBeChecked(); await page.getByRole('button', { name: /save/i }).click(); + await expect(page.getByText('OIDC client updated successfully', { exact: true })).toBeVisible(); await page.reload(); await expect(page.getByRole('switch', { name: 'Skip Consent Screen' })).not.toBeChecked(); diff --git a/tests/specs/scim.spec.ts b/tests/specs/scim.spec.ts index 2b7ccafe..8b775d84 100644 --- a/tests/specs/scim.spec.ts +++ b/tests/specs/scim.spec.ts @@ -5,7 +5,7 @@ import { oidcClients, userGroups, users } from '../data'; async function configureOidcClient(page: Page) { await page.goto(`/settings/admin/oidc-clients/${oidcClients.scim.id}`); - await page.getByText('SCIM Provisioning', { exact: true }).click(); + await page.getByRole('tab', { name: 'SCIM Provisioning' }).click(); await page .getByLabel('SCIM Endpoint') @@ -16,6 +16,7 @@ async function configureOidcClient(page: Page) { async function syncScimServiceProvider(page: Page) { await page.goto(`/settings/admin/oidc-clients/${oidcClients.scim.id}`); + await page.getByRole('tab', { name: 'SCIM Provisioning' }).click(); await page.getByRole('button', { name: 'Sync now' }).click(); await page.waitForSelector('[data-type="success"]'); @@ -29,7 +30,7 @@ test.describe('SCIM Configuration', () => { test('Enable SCIM for OIDC client', async ({ page }) => { await page.goto(`/settings/admin/oidc-clients/${oidcClients.scim.id}`); - await page.getByText('SCIM Provisioning', { exact: true }).click(); + await page.getByRole('tab', { name: 'SCIM Provisioning' }).click(); await page.getByLabel('SCIM Endpoint').fill('http://scim.provider/api'); await page.getByLabel('SCIM Token').fill('supersecrettoken'); @@ -48,11 +49,12 @@ test.describe('SCIM Configuration', () => { await configureOidcClient(page); await page.goto(`/settings/admin/oidc-clients/${oidcClients.scim.id}`); + await page.getByRole('tab', { name: 'SCIM Provisioning' }).click(); await page.getByLabel('SCIM Endpoint').fill('http://new.scim.provider/api'); await page.getByLabel('SCIM Token').fill('evenmoresecrettoken'); - await page.getByRole('button', { name: 'Save' }).nth(1).click(); + await page.getByRole('button', { name: 'Save' }).click(); await expect(page.locator('[data-type="success"]')).toHaveText( 'SCIM configuration updated successfully.' @@ -68,6 +70,7 @@ test.describe('SCIM Configuration', () => { await configureOidcClient(page); await page.goto(`/settings/admin/oidc-clients/${oidcClients.scim.id}`); + await page.getByRole('tab', { name: 'SCIM Provisioning' }).click(); await page.getByRole('button', { name: 'Disable' }).click(); await page.getByRole('button', { name: 'Disable' }).nth(1).click(); @@ -123,15 +126,16 @@ test.describe('SCIM Sync', () => { test('Remove allowed group and sync', async ({ page }) => { await syncScimServiceProvider(page); - await page.getByRole('button', { name: 'Expand card' }).first().click(); + await page.getByRole('tab', { name: 'Allowed user groups' }).click(); - await page + const developersCheckbox = page .getByRole('row', { name: userGroups.developers.name }) - .getByRole('cell') - .first() - .click(); + .getByRole('checkbox'); + await developersCheckbox.click(); + await expect(developersCheckbox).toHaveAttribute('data-state', 'unchecked'); - await page.getByRole('button', { name: 'Save' }).nth(1).click(); + await page.getByRole('button', { name: 'Save' }).click(); + await expect(page.getByText('Allowed user groups updated successfully', { exact: true })).toBeVisible(); await syncScimServiceProvider(page); @@ -149,7 +153,7 @@ test.describe('SCIM Sync', () => { test('Remove group restrictions and sync', async ({ page }) => { await syncScimServiceProvider(page); - await page.getByRole('button', { name: 'Expand card' }).first().click(); + await page.getByRole('tab', { name: 'Allowed user groups' }).click(); await page.getByRole('button', { name: 'Unrestrict' }).click(); await page.getByRole('button', { name: 'Unrestrict' }).nth(1).click(); diff --git a/tests/specs/user-group.spec.ts b/tests/specs/user-group.spec.ts index 590cce1a..a58b08e5 100644 --- a/tests/specs/user-group.spec.ts +++ b/tests/specs/user-group.spec.ts @@ -44,11 +44,12 @@ test('Edit user group', async ({ page }) => { test('Update user group users', async ({ page }) => { const group = userGroups.designers; await page.goto(`/settings/admin/user-groups/${group.id}`); + await page.getByRole('tab', { name: 'Users' }).click(); await page.getByRole('row', { name: users.tim.username }).getByRole('checkbox').click(); await page.getByRole('row', { name: users.craig.username }).getByRole('checkbox').click(); - await page.getByRole('button', { name: 'Save' }).nth(1).click(); + await page.getByRole('button', { name: 'Save' }).click(); await expect(page.locator('[data-type="success"]')).toHaveText('Users updated successfully'); @@ -77,7 +78,7 @@ test('Delete user group', async ({ page }) => { test('Update user group custom claims', async ({ page }) => { await page.goto(`/settings/admin/user-groups/${userGroups.designers.id}`); - await page.getByRole('button', { name: 'Expand card' }).first().click(); + await page.getByRole('tab', { name: 'Custom Claims' }).click(); // Add two custom claims await page.getByRole('button', { name: 'Add custom claim' }).click(); @@ -89,7 +90,7 @@ test('Update user group custom claims', async ({ page }) => { await page.getByPlaceholder('Key').nth(1).fill('customClaim2'); await page.getByPlaceholder('Value').nth(1).fill('customClaim2_value'); - await page.getByRole('button', { name: 'Save' }).nth(2).click(); + await page.getByRole('button', { name: 'Save' }).click(); await expect(page.locator('[data-type="success"]')).toHaveText( 'Custom claims updated successfully' @@ -106,7 +107,7 @@ test('Update user group custom claims', async ({ page }) => { // Remove one custom claim await page.getByLabel('Remove custom claim').first().click(); - await page.getByRole('button', { name: 'Save' }).nth(2).click(); + await page.getByRole('button', { name: 'Save' }).click(); await expect(page.locator('[data-type="success"]')).toHaveText( 'Custom claims updated successfully' @@ -123,7 +124,7 @@ test('Update user group custom claims', async ({ page }) => { test('Update user group allowed user groups', async ({ page }) => { await page.goto(`/settings/admin/user-groups/${userGroups.designers.id}`); - await page.getByRole('button', { name: 'Expand card' }).nth(1).click(); + await page.getByRole('tab', { name: 'Allowed OIDC Clients' }).click(); // Unrestricted OIDC clients should be checked and disabled const nextcloudRow = page @@ -135,7 +136,7 @@ test('Update user group allowed user groups', async ({ page }) => { await page.getByRole('row', { name: oidcClients.tailscale.name }).getByRole('checkbox').click(); await page.getByRole('row', { name: oidcClients.immich.name }).getByRole('checkbox').click(); - await page.getByRole('button', { name: 'Save' }).nth(2).click(); + await page.getByRole('button', { name: 'Save' }).click(); await expect(page.locator('[data-type="success"]')).toHaveText( 'Allowed OIDC clients updated successfully' diff --git a/tests/specs/user-settings.spec.ts b/tests/specs/user-settings.spec.ts index 84cd1d87..38ee694b 100644 --- a/tests/specs/user-settings.spec.ts +++ b/tests/specs/user-settings.spec.ts @@ -187,7 +187,7 @@ test('Update user fails with already taken username in different casing', async test('Update user custom claims', async ({ page }) => { await page.goto(`/settings/admin/users/${users.craig.id}`); - await page.getByRole('button', { name: 'Expand card' }).nth(1).click(); + await page.getByRole('tab', { name: 'Custom Claims' }).click(); // Add two custom claims await page.getByRole('button', { name: 'Add custom claim' }).click(); @@ -199,7 +199,7 @@ test('Update user custom claims', async ({ page }) => { await page.getByPlaceholder('Key').nth(1).fill('customClaim2'); await page.getByPlaceholder('Value').nth(1).fill('customClaim2_value'); - await page.getByRole('button', { name: 'Save' }).nth(1).click(); + await page.getByRole('button', { name: 'Save' }).click(); await expect(page.locator('[data-type="success"]')).toHaveText( 'Custom claims updated successfully' @@ -215,7 +215,7 @@ test('Update user custom claims', async ({ page }) => { // Remove one custom claim await page.getByLabel('Remove custom claim').first().click(); - await page.getByRole('button', { name: 'Save' }).nth(1).click(); + await page.getByRole('button', { name: 'Save' }).click(); await expect(page.locator('[data-type="success"]')).toHaveText( 'Custom claims updated successfully' @@ -232,12 +232,12 @@ test('Update user group assignments', async ({ page }) => { const user = users.craig; await page.goto(`/settings/admin/users/${user.id}`); - page.getByRole('button', { name: 'Expand card' }).first().click(); + await page.getByRole('tab', { name: 'User Groups' }).click(); await page.getByRole('row', { name: userGroups.developers.name }).getByRole('checkbox').click(); await page.getByRole('row', { name: userGroups.designers.name }).getByRole('checkbox').click(); - await page.getByRole('button', { name: 'Save' }).nth(1).click(); + await page.getByRole('button', { name: 'Save' }).click(); await expect(page.locator('[data-type="success"]')).toHaveText( 'User groups updated successfully' @@ -255,6 +255,7 @@ test('Update user group assignments', async ({ page }) => { test('Admin can view another user passkeys', async ({ page }) => { await page.goto(`/settings/admin/users/${users.craig.id}`); + await page.getByRole('tab', { name: 'Passkeys' }).click(); await expect(page.getByText('Passkey 2')).toBeVisible(); await expect(page.getByText(/Added on/)).toBeVisible(); @@ -262,8 +263,13 @@ test('Admin can view another user passkeys', async ({ page }) => { test('Admin can delete another user passkey and audit log is created', async ({ page }) => { await page.goto(`/settings/admin/users/${users.craig.id}`); + await page.getByRole('tab', { name: 'Passkeys' }).click(); - await page.locator('[data-slot="item"]').filter({ hasText: 'Passkey 2' }).getByLabel('Delete').click(); + await page + .locator('[data-slot="item"]') + .filter({ hasText: 'Passkey 2' }) + .getByLabel('Delete') + .click(); await page.getByRole('alertdialog').getByRole('button', { name: 'Delete' }).click(); await expect(page.locator('[data-type="success"]')).toHaveText('Passkey deleted successfully'); diff --git a/tests/specs/user-signup.spec.ts b/tests/specs/user-signup.spec.ts index 1ad2ae16..2871bfc1 100644 --- a/tests/specs/user-signup.spec.ts +++ b/tests/specs/user-signup.spec.ts @@ -10,10 +10,10 @@ async function setSignupMode( ) { await page.goto('/settings/admin/application-configuration'); - await page.getByRole('button', { name: 'Expand card' }).nth(1).click(); + await page.getByRole('tab', { name: 'User Creation' }).click(); await page.getByRole('button', { name: 'Enable User Signups' }).click(); await page.getByRole('option', { name: mode }).click(); - await page.getByRole('button', { name: 'Save' }).nth(1).click(); + await page.getByRole('button', { name: 'Save' }).click(); await expect(page.locator('[data-type="success"]').last()).toHaveText( 'User creation settings updated successfully.'