mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-08-09 12:40:59 +00:00
Merge remote-tracking branch 'origin/master' into dev/4.11.x
This commit is contained in:
@@ -313,18 +313,20 @@ export default function WorkspaceSettingsPanel({
|
||||
<ItemMedia variant="icon">
|
||||
<Users className="size-4" />
|
||||
</ItemMedia>
|
||||
<ItemContent>
|
||||
<ItemContent className="min-w-0">
|
||||
<ItemTitle>
|
||||
{member.email}
|
||||
{member.display_name}
|
||||
{isSelf && (
|
||||
<Badge variant="outline">{t('workspace.you')}</Badge>
|
||||
)}
|
||||
</ItemTitle>
|
||||
<ItemDescription>
|
||||
{t(`workspace.roles.${member.role}`)}
|
||||
<ItemDescription className="flex flex-wrap items-center gap-x-1.5 gap-y-0.5">
|
||||
<span className="break-all">{member.email}</span>
|
||||
<span aria-hidden="true">·</span>
|
||||
<span>{t(`workspace.roles.${member.role}`)}</span>
|
||||
</ItemDescription>
|
||||
</ItemContent>
|
||||
<ItemActions>
|
||||
<ItemActions className="max-sm:basis-full max-sm:justify-end max-sm:pl-10">
|
||||
{canUpdateMembers && member.role !== 'owner' && (
|
||||
<Select
|
||||
value={member.role}
|
||||
|
||||
@@ -19,6 +19,7 @@ export interface WorkspaceMembership {
|
||||
uuid: string;
|
||||
workspace_uuid: string;
|
||||
account_uuid: string;
|
||||
display_name: string;
|
||||
email: string;
|
||||
role: WorkspaceRole;
|
||||
status: 'active' | 'disabled' | 'removed';
|
||||
|
||||
@@ -81,6 +81,7 @@ export interface WorkspaceEntryMock {
|
||||
uuid: string;
|
||||
workspace_uuid: string;
|
||||
account_uuid: string;
|
||||
display_name: string;
|
||||
email: string;
|
||||
role: 'owner' | 'admin' | 'developer' | 'operator' | 'viewer';
|
||||
status: 'active';
|
||||
@@ -156,6 +157,7 @@ export function makeWorkspaceEntry(
|
||||
uuid: `membership-${uuid}`,
|
||||
workspace_uuid: uuid,
|
||||
account_uuid: 'account-playwright',
|
||||
display_name: 'Playwright Admin',
|
||||
email: 'admin@example.com',
|
||||
role: 'owner',
|
||||
status: 'active',
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import {
|
||||
installLangBotApiMocks,
|
||||
makeWorkspaceEntry,
|
||||
} from './fixtures/langbot-api';
|
||||
|
||||
const members = [
|
||||
{
|
||||
uuid: 'membership-owner',
|
||||
workspace_uuid: 'workspace-email-test',
|
||||
account_uuid: 'account-playwright',
|
||||
display_name: 'RockChinQ',
|
||||
email: 'rock@example.com',
|
||||
role: 'owner',
|
||||
status: 'active',
|
||||
joined_at: '2026-08-01T00:00:00Z',
|
||||
created_at: '2026-08-01T00:00:00Z',
|
||||
},
|
||||
{
|
||||
uuid: 'membership-admin',
|
||||
workspace_uuid: 'workspace-email-test',
|
||||
account_uuid: 'account-admin',
|
||||
display_name: 'Junyan Qin',
|
||||
email: 'a.very.long.workspace.member.email.address@example-company.test',
|
||||
role: 'admin',
|
||||
status: 'active',
|
||||
joined_at: '2026-08-02T00:00:00Z',
|
||||
created_at: '2026-08-02T00:00:00Z',
|
||||
},
|
||||
];
|
||||
|
||||
test.use({ viewport: { width: 390, height: 844 } });
|
||||
|
||||
test('workspace member list displays each member email without overlapping controls', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
const workspace = makeWorkspaceEntry(
|
||||
'workspace-email-test',
|
||||
'Email Test Workspace',
|
||||
'cloud_projection',
|
||||
);
|
||||
workspace.membership.display_name = 'RockChinQ';
|
||||
workspace.membership.email = 'rock@example.com';
|
||||
|
||||
await installLangBotApiMocks(page, {
|
||||
authenticated: true,
|
||||
workspaces: [workspace],
|
||||
});
|
||||
await page.route(
|
||||
'**/api/v1/workspaces/workspace-email-test/members',
|
||||
(route) =>
|
||||
route.fulfill({
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ code: 0, message: 'ok', data: { members } }),
|
||||
}),
|
||||
);
|
||||
await page.route(
|
||||
'**/api/v1/workspaces/workspace-email-test/invitations',
|
||||
(route) =>
|
||||
route.fulfill({
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
code: 0,
|
||||
message: 'ok',
|
||||
data: { invitations: [] },
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
await page.goto('/home?action=showWorkspaceSettings');
|
||||
|
||||
await expect(page.getByText('RockChinQ')).toBeVisible();
|
||||
await expect(page.getByText('rock@example.com')).toBeVisible();
|
||||
await expect(page.getByText('Junyan Qin')).toBeVisible();
|
||||
const longEmail = page.getByText(
|
||||
'a.very.long.workspace.member.email.address@example-company.test',
|
||||
);
|
||||
await expect(longEmail).toBeVisible();
|
||||
await longEmail.scrollIntoViewIfNeeded();
|
||||
|
||||
const roleSelect = page.getByRole('combobox').last();
|
||||
const [emailBox, selectBox] = await Promise.all([
|
||||
longEmail.boundingBox(),
|
||||
roleSelect.boundingBox(),
|
||||
]);
|
||||
expect(emailBox).not.toBeNull();
|
||||
expect(selectBox).not.toBeNull();
|
||||
const overlaps =
|
||||
emailBox!.x < selectBox!.x + selectBox!.width &&
|
||||
emailBox!.x + emailBox!.width > selectBox!.x &&
|
||||
emailBox!.y < selectBox!.y + selectBox!.height &&
|
||||
emailBox!.y + emailBox!.height > selectBox!.y;
|
||||
expect(overlaps).toBe(false);
|
||||
expect(selectBox!.y).toBeGreaterThanOrEqual(emailBox!.y + emailBox!.height);
|
||||
await roleSelect.scrollIntoViewIfNeeded();
|
||||
|
||||
await page.screenshot({
|
||||
path: testInfo.outputPath('workspace-member-emails-mobile.png'),
|
||||
fullPage: true,
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import test from 'node:test';
|
||||
|
||||
const panelSource = await readFile(
|
||||
new URL(
|
||||
'../../src/app/home/components/workspace-settings/WorkspaceSettingsPanel.tsx',
|
||||
import.meta.url,
|
||||
),
|
||||
'utf8',
|
||||
);
|
||||
const entitySource = await readFile(
|
||||
new URL('../../src/app/infra/entities/workspace.ts', import.meta.url),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
test('workspace member rows show display name, email, and role', () => {
|
||||
assert.match(entitySource, /display_name:\s*string/);
|
||||
assert.match(panelSource, /\{member\.display_name\}/);
|
||||
assert.match(
|
||||
panelSource,
|
||||
/<ItemDescription[^>]*>[\s\S]*?\{member\.email\}[\s\S]*?workspace\.roles\.\$\{member\.role\}[\s\S]*?<\/ItemDescription>/,
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user