kopia lustrzana https://gitlab.com/soapbox-pub/soapbox
Merge branch 'group-tests' into 'develop'
Group tests See merge request soapbox-pub/soapbox!2502environments/review-develop-3zknud/deployments/3342
commit
a35d95e9e3
|
@ -0,0 +1,46 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { buildGroup } from 'soapbox/jest/factory';
|
||||||
|
import { render, screen } from 'soapbox/jest/test-helpers';
|
||||||
|
import { Group } from 'soapbox/types/entities';
|
||||||
|
|
||||||
|
import GroupHeader from '../group-header';
|
||||||
|
|
||||||
|
let group: Group;
|
||||||
|
|
||||||
|
describe('<GroupHeader />', () => {
|
||||||
|
describe('without a group', () => {
|
||||||
|
it('should render the blankslate', () => {
|
||||||
|
render(<GroupHeader group={null} />);
|
||||||
|
expect(screen.getByTestId('group-header-missing')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the Group has been deleted', () => {
|
||||||
|
it('only shows name, header, and avatar', () => {
|
||||||
|
group = buildGroup({ display_name: 'my group', deleted_at: new Date().toISOString() });
|
||||||
|
render(<GroupHeader group={group} />);
|
||||||
|
|
||||||
|
expect(screen.queryAllByTestId('group-header-missing')).toHaveLength(0);
|
||||||
|
expect(screen.queryAllByTestId('group-actions')).toHaveLength(0);
|
||||||
|
expect(screen.queryAllByTestId('group-meta')).toHaveLength(0);
|
||||||
|
expect(screen.getByTestId('group-header-image')).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId('group-avatar')).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId('group-name')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with a valid Group', () => {
|
||||||
|
it('only shows all fields', () => {
|
||||||
|
group = buildGroup({ display_name: 'my group', deleted_at: null });
|
||||||
|
render(<GroupHeader group={group} />);
|
||||||
|
|
||||||
|
expect(screen.queryAllByTestId('group-header-missing')).toHaveLength(0);
|
||||||
|
expect(screen.getByTestId('group-actions')).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId('group-meta')).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId('group-header-image')).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId('group-avatar')).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId('group-name')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,320 @@
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { __stub } from 'soapbox/api';
|
||||||
|
import { buildGroup, buildGroupMember, buildGroupRelationship } from 'soapbox/jest/factory';
|
||||||
|
import { render, screen, waitFor } from 'soapbox/jest/test-helpers';
|
||||||
|
import { GroupRoles } from 'soapbox/schemas/group-member';
|
||||||
|
|
||||||
|
import GroupMemberListItem from '../group-member-list-item';
|
||||||
|
|
||||||
|
describe('<GroupMemberListItem />', () => {
|
||||||
|
describe('account rendering', () => {
|
||||||
|
const accountId = '4';
|
||||||
|
const groupMember = buildGroupMember({}, {
|
||||||
|
id: accountId,
|
||||||
|
display_name: 'tiger woods',
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
__stub((mock) => {
|
||||||
|
mock.onGet(`/api/v1/accounts/${accountId}`).reply(200, groupMember.account);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render the users avatar', async () => {
|
||||||
|
const group = buildGroup({
|
||||||
|
relationship: buildGroupRelationship(),
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<GroupMemberListItem group={group} member={groupMember} canPromoteToAdmin />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId('group-member-list-item')).toHaveTextContent(groupMember.account.display_name);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('role badge', () => {
|
||||||
|
const accountId = '4';
|
||||||
|
const group = buildGroup();
|
||||||
|
|
||||||
|
describe('when the user is an Owner', () => {
|
||||||
|
const groupMember = buildGroupMember({ role: GroupRoles.OWNER }, {
|
||||||
|
id: accountId,
|
||||||
|
display_name: 'tiger woods',
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
__stub((mock) => {
|
||||||
|
mock.onGet(`/api/v1/accounts/${accountId}`).reply(200, groupMember.account);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render the correct badge', async () => {
|
||||||
|
render(<GroupMemberListItem group={group} member={groupMember} canPromoteToAdmin />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId('role-badge')).toHaveTextContent('owner');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the user is an Admin', () => {
|
||||||
|
const groupMember = buildGroupMember({ role: GroupRoles.ADMIN }, {
|
||||||
|
id: accountId,
|
||||||
|
display_name: 'tiger woods',
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
__stub((mock) => {
|
||||||
|
mock.onGet(`/api/v1/accounts/${accountId}`).reply(200, groupMember.account);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render the correct badge', async () => {
|
||||||
|
render(<GroupMemberListItem group={group} member={groupMember} canPromoteToAdmin />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId('role-badge')).toHaveTextContent('admin');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the user is an User', () => {
|
||||||
|
const groupMember = buildGroupMember({ role: GroupRoles.USER }, {
|
||||||
|
id: accountId,
|
||||||
|
display_name: 'tiger woods',
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
__stub((mock) => {
|
||||||
|
mock.onGet(`/api/v1/accounts/${accountId}`).reply(200, groupMember.account);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render no correct badge', async () => {
|
||||||
|
render(<GroupMemberListItem group={group} member={groupMember} canPromoteToAdmin />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryAllByTestId('role-badge')).toHaveLength(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('as a Group owner', () => {
|
||||||
|
const group = buildGroup({
|
||||||
|
relationship: buildGroupRelationship({
|
||||||
|
role: GroupRoles.OWNER,
|
||||||
|
member: true,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the user has role of "user"', () => {
|
||||||
|
const accountId = '4';
|
||||||
|
const groupMember = buildGroupMember({}, {
|
||||||
|
id: accountId,
|
||||||
|
display_name: 'tiger woods',
|
||||||
|
username: 'tiger',
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
__stub((mock) => {
|
||||||
|
mock.onGet(`/api/v1/accounts/${accountId}`).reply(200, groupMember.account);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when "canPromoteToAdmin is true', () => {
|
||||||
|
it('should render dropdown with correct Owner actions', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
|
||||||
|
render(<GroupMemberListItem group={group} member={groupMember} canPromoteToAdmin />);
|
||||||
|
|
||||||
|
await waitFor(async() => {
|
||||||
|
await user.click(screen.getByTestId('icon-button'));
|
||||||
|
});
|
||||||
|
|
||||||
|
const dropdownMenu = screen.getByTestId('dropdown-menu');
|
||||||
|
expect(dropdownMenu).toHaveTextContent('Assign admin role');
|
||||||
|
expect(dropdownMenu).toHaveTextContent('Kick @tiger from group');
|
||||||
|
expect(dropdownMenu).toHaveTextContent('Ban from group');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when "canPromoteToAdmin is false', () => {
|
||||||
|
it('should prevent promoting user to Admin', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
|
||||||
|
render(<GroupMemberListItem group={group} member={groupMember} canPromoteToAdmin={false} />);
|
||||||
|
|
||||||
|
await waitFor(async() => {
|
||||||
|
await user.click(screen.getByTestId('icon-button'));
|
||||||
|
await user.click(screen.getByTitle('Assign admin role'));
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByTestId('toast')).toHaveTextContent('Admin limit reached');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the user has role of "admin"', () => {
|
||||||
|
const accountId = '4';
|
||||||
|
const groupMember = buildGroupMember(
|
||||||
|
{
|
||||||
|
role: GroupRoles.ADMIN,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: accountId,
|
||||||
|
display_name: 'tiger woods',
|
||||||
|
username: 'tiger',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
__stub((mock) => {
|
||||||
|
mock.onGet(`/api/v1/accounts/${accountId}`).reply(200, groupMember.account);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render dropdown with correct Owner actions', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
|
||||||
|
render(<GroupMemberListItem group={group} member={groupMember} canPromoteToAdmin />);
|
||||||
|
|
||||||
|
await waitFor(async() => {
|
||||||
|
await user.click(screen.getByTestId('icon-button'));
|
||||||
|
});
|
||||||
|
|
||||||
|
const dropdownMenu = screen.getByTestId('dropdown-menu');
|
||||||
|
expect(dropdownMenu).toHaveTextContent('Remove admin role');
|
||||||
|
expect(dropdownMenu).toHaveTextContent('Kick @tiger from group');
|
||||||
|
expect(dropdownMenu).toHaveTextContent('Ban from group');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('as a Group admin', () => {
|
||||||
|
const group = buildGroup({
|
||||||
|
relationship: buildGroupRelationship({
|
||||||
|
role: GroupRoles.ADMIN,
|
||||||
|
member: true,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the user has role of "user"', () => {
|
||||||
|
const accountId = '4';
|
||||||
|
const groupMember = buildGroupMember({}, {
|
||||||
|
id: accountId,
|
||||||
|
display_name: 'tiger woods',
|
||||||
|
username: 'tiger',
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
__stub((mock) => {
|
||||||
|
mock.onGet(`/api/v1/accounts/${accountId}`).reply(200, groupMember.account);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render dropdown with correct Admin actions', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
|
||||||
|
render(<GroupMemberListItem group={group} member={groupMember} canPromoteToAdmin />);
|
||||||
|
|
||||||
|
await waitFor(async() => {
|
||||||
|
await user.click(screen.getByTestId('icon-button'));
|
||||||
|
});
|
||||||
|
|
||||||
|
const dropdownMenu = screen.getByTestId('dropdown-menu');
|
||||||
|
expect(dropdownMenu).not.toHaveTextContent('Assign admin role');
|
||||||
|
expect(dropdownMenu).toHaveTextContent('Kick @tiger from group');
|
||||||
|
expect(dropdownMenu).toHaveTextContent('Ban from group');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the user has role of "admin"', () => {
|
||||||
|
const accountId = '4';
|
||||||
|
const groupMember = buildGroupMember(
|
||||||
|
{
|
||||||
|
role: GroupRoles.ADMIN,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: accountId,
|
||||||
|
display_name: 'tiger woods',
|
||||||
|
username: 'tiger',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
__stub((mock) => {
|
||||||
|
mock.onGet(`/api/v1/accounts/${accountId}`).reply(200, groupMember.account);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not render the dropdown', async () => {
|
||||||
|
render(<GroupMemberListItem group={group} member={groupMember} canPromoteToAdmin />);
|
||||||
|
|
||||||
|
await waitFor(async() => {
|
||||||
|
expect(screen.queryAllByTestId('icon-button')).toHaveLength(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the user has role of "owner"', () => {
|
||||||
|
const accountId = '4';
|
||||||
|
const groupMember = buildGroupMember(
|
||||||
|
{
|
||||||
|
role: GroupRoles.OWNER,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: accountId,
|
||||||
|
display_name: 'tiger woods',
|
||||||
|
username: 'tiger',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
__stub((mock) => {
|
||||||
|
mock.onGet(`/api/v1/accounts/${accountId}`).reply(200, groupMember.account);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not render the dropdown', async () => {
|
||||||
|
render(<GroupMemberListItem group={group} member={groupMember} canPromoteToAdmin />);
|
||||||
|
|
||||||
|
await waitFor(async() => {
|
||||||
|
expect(screen.queryAllByTestId('icon-button')).toHaveLength(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('as a Group user', () => {
|
||||||
|
const group = buildGroup({
|
||||||
|
relationship: buildGroupRelationship({
|
||||||
|
role: GroupRoles.USER,
|
||||||
|
member: true,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const accountId = '4';
|
||||||
|
const groupMember = buildGroupMember({}, {
|
||||||
|
id: accountId,
|
||||||
|
display_name: 'tiger woods',
|
||||||
|
username: 'tiger',
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
__stub((mock) => {
|
||||||
|
mock.onGet(`/api/v1/accounts/${accountId}`).reply(200, groupMember.account);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not render the dropdown', async () => {
|
||||||
|
render(<GroupMemberListItem group={group} member={groupMember} canPromoteToAdmin />);
|
||||||
|
|
||||||
|
await waitFor(async() => {
|
||||||
|
expect(screen.queryAllByTestId('icon-button')).toHaveLength(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,123 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { buildGroup, buildGroupTag, buildGroupRelationship } from 'soapbox/jest/factory';
|
||||||
|
import { render, screen } from 'soapbox/jest/test-helpers';
|
||||||
|
import { GroupRoles } from 'soapbox/schemas/group-member';
|
||||||
|
|
||||||
|
import GroupTagListItem from '../group-tag-list-item';
|
||||||
|
|
||||||
|
describe('<GroupTagListItem />', () => {
|
||||||
|
describe('tag name', () => {
|
||||||
|
const name = 'hello';
|
||||||
|
|
||||||
|
it('should render the tag name', () => {
|
||||||
|
const group = buildGroup();
|
||||||
|
const tag = buildGroupTag({ name });
|
||||||
|
render(<GroupTagListItem group={group} tag={tag} isPinnable />);
|
||||||
|
|
||||||
|
expect(screen.getByTestId('group-tag-list-item')).toHaveTextContent(`#${name}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the tag is "visible"', () => {
|
||||||
|
const group = buildGroup();
|
||||||
|
const tag = buildGroupTag({ name, visible: true });
|
||||||
|
|
||||||
|
it('renders the default name', () => {
|
||||||
|
render(<GroupTagListItem group={group} tag={tag} isPinnable />);
|
||||||
|
expect(screen.getByTestId('group-tag-name')).toHaveClass('text-gray-900');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the tag is not "visible" and user is Owner', () => {
|
||||||
|
const group = buildGroup({
|
||||||
|
relationship: buildGroupRelationship({
|
||||||
|
role: GroupRoles.OWNER,
|
||||||
|
member: true,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const tag = buildGroupTag({
|
||||||
|
name,
|
||||||
|
visible: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the subtle name', () => {
|
||||||
|
render(<GroupTagListItem group={group} tag={tag} isPinnable />);
|
||||||
|
expect(screen.getByTestId('group-tag-name')).toHaveClass('text-gray-400');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the tag is not "visible" and user is Admin or User', () => {
|
||||||
|
const group = buildGroup({
|
||||||
|
relationship: buildGroupRelationship({
|
||||||
|
role: GroupRoles.ADMIN,
|
||||||
|
member: true,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const tag = buildGroupTag({
|
||||||
|
name,
|
||||||
|
visible: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the subtle name', () => {
|
||||||
|
render(<GroupTagListItem group={group} tag={tag} isPinnable />);
|
||||||
|
expect(screen.getByTestId('group-tag-name')).toHaveClass('text-gray-900');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('pinning', () => {
|
||||||
|
describe('as an owner', () => {
|
||||||
|
const group = buildGroup({
|
||||||
|
relationship: buildGroupRelationship({
|
||||||
|
role: GroupRoles.OWNER,
|
||||||
|
member: true,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the tag is visible', () => {
|
||||||
|
const tag = buildGroupTag({ visible: true });
|
||||||
|
|
||||||
|
it('renders the pin icon', () => {
|
||||||
|
render(<GroupTagListItem group={group} tag={tag} isPinnable />);
|
||||||
|
expect(screen.getByTestId('pin-icon')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the tag is not visible', () => {
|
||||||
|
const tag = buildGroupTag({ visible: false });
|
||||||
|
|
||||||
|
it('does not render the pin icon', () => {
|
||||||
|
render(<GroupTagListItem group={group} tag={tag} isPinnable />);
|
||||||
|
expect(screen.queryAllByTestId('pin-icon')).toHaveLength(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('as a non-owner', () => {
|
||||||
|
const group = buildGroup({
|
||||||
|
relationship: buildGroupRelationship({
|
||||||
|
role: GroupRoles.ADMIN,
|
||||||
|
member: true,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the tag is visible', () => {
|
||||||
|
const tag = buildGroupTag({ visible: true });
|
||||||
|
|
||||||
|
it('does not render the pin icon', () => {
|
||||||
|
render(<GroupTagListItem group={group} tag={tag} isPinnable />);
|
||||||
|
expect(screen.queryAllByTestId('pin-icon')).toHaveLength(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the tag is not visible', () => {
|
||||||
|
const tag = buildGroupTag({ visible: false });
|
||||||
|
|
||||||
|
it('does not render the pin icon', () => {
|
||||||
|
render(<GroupTagListItem group={group} tag={tag} isPinnable />);
|
||||||
|
expect(screen.queryAllByTestId('pin-icon')).toHaveLength(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -34,7 +34,7 @@ const GroupHeader: React.FC<IGroupHeader> = ({ group }) => {
|
||||||
|
|
||||||
if (!group) {
|
if (!group) {
|
||||||
return (
|
return (
|
||||||
<div className='-mx-4 -mt-4 sm:-mx-6 sm:-mt-6'>
|
<div className='-mx-4 -mt-4 sm:-mx-6 sm:-mt-6' data-testid='group-header-missing'>
|
||||||
<div>
|
<div>
|
||||||
<div className='relative h-32 w-full bg-gray-200 dark:bg-gray-900/50 md:rounded-t-xl lg:h-48' />
|
<div className='relative h-32 w-full bg-gray-200 dark:bg-gray-900/50 md:rounded-t-xl lg:h-48' />
|
||||||
</div>
|
</div>
|
||||||
|
@ -107,7 +107,10 @@ const GroupHeader: React.FC<IGroupHeader> = ({ group }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex h-32 w-full items-center justify-center bg-gray-200 dark:bg-gray-800/30 md:rounded-t-xl lg:h-52'>
|
<div
|
||||||
|
data-testid='group-header-image'
|
||||||
|
className='flex h-32 w-full items-center justify-center bg-gray-200 dark:bg-gray-800/30 md:rounded-t-xl lg:h-52'
|
||||||
|
>
|
||||||
{isHeaderMissing ? (
|
{isHeaderMissing ? (
|
||||||
<Icon src={require('@tabler/icons/photo-off.svg')} className='h-6 w-6 text-gray-500 dark:text-gray-700' />
|
<Icon src={require('@tabler/icons/photo-off.svg')} className='h-6 w-6 text-gray-500 dark:text-gray-700' />
|
||||||
) : header}
|
) : header}
|
||||||
|
@ -120,7 +123,7 @@ const GroupHeader: React.FC<IGroupHeader> = ({ group }) => {
|
||||||
<div className='relative'>
|
<div className='relative'>
|
||||||
{renderHeader()}
|
{renderHeader()}
|
||||||
|
|
||||||
<div className='absolute bottom-0 left-1/2 -translate-x-1/2 translate-y-1/2'>
|
<div className='absolute bottom-0 left-1/2 -translate-x-1/2 translate-y-1/2' data-testid='group-avatar'>
|
||||||
<a href={group.avatar} onClick={handleAvatarClick} target='_blank'>
|
<a href={group.avatar} onClick={handleAvatarClick} target='_blank'>
|
||||||
<GroupAvatar
|
<GroupAvatar
|
||||||
group={group}
|
group={group}
|
||||||
|
@ -136,11 +139,12 @@ const GroupHeader: React.FC<IGroupHeader> = ({ group }) => {
|
||||||
size='xl'
|
size='xl'
|
||||||
weight='bold'
|
weight='bold'
|
||||||
dangerouslySetInnerHTML={{ __html: group.display_name_html }}
|
dangerouslySetInnerHTML={{ __html: group.display_name_html }}
|
||||||
|
data-testid='group-name'
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!isDeleted && (
|
{!isDeleted && (
|
||||||
<>
|
<>
|
||||||
<Stack space={1} alignItems='center'>
|
<Stack data-testid='group-meta' space={1} alignItems='center'>
|
||||||
<HStack className='text-gray-700 dark:text-gray-600' space={2} wrap>
|
<HStack className='text-gray-700 dark:text-gray-600' space={2} wrap>
|
||||||
<GroupRelationship group={group} />
|
<GroupRelationship group={group} />
|
||||||
<GroupPrivacy group={group} />
|
<GroupPrivacy group={group} />
|
||||||
|
@ -154,7 +158,7 @@ const GroupHeader: React.FC<IGroupHeader> = ({ group }) => {
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<HStack alignItems='center' space={2}>
|
<HStack alignItems='center' space={2} data-testid='group-actions'>
|
||||||
<GroupOptionsButton group={group} />
|
<GroupOptionsButton group={group} />
|
||||||
<GroupActionButton group={group} />
|
<GroupActionButton group={group} />
|
||||||
</HStack>
|
</HStack>
|
||||||
|
|
|
@ -180,7 +180,11 @@ const GroupMemberListItem = (props: IGroupMemberListItem) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HStack alignItems='center' justifyContent='between'>
|
<HStack
|
||||||
|
alignItems='center'
|
||||||
|
justifyContent='between'
|
||||||
|
data-testid='group-member-list-item'
|
||||||
|
>
|
||||||
<div className='w-full'>
|
<div className='w-full'>
|
||||||
<Account account={member.account} withRelationship={false} />
|
<Account account={member.account} withRelationship={false} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -188,6 +192,7 @@ const GroupMemberListItem = (props: IGroupMemberListItem) => {
|
||||||
<HStack alignItems='center' space={2}>
|
<HStack alignItems='center' space={2}>
|
||||||
{(isMemberOwner || isMemberAdmin) ? (
|
{(isMemberOwner || isMemberAdmin) ? (
|
||||||
<span
|
<span
|
||||||
|
data-testid='role-badge'
|
||||||
className={
|
className={
|
||||||
clsx('inline-flex items-center rounded px-2 py-1 text-xs font-medium capitalize', {
|
clsx('inline-flex items-center rounded px-2 py-1 text-xs font-medium capitalize', {
|
||||||
'bg-primary-200 text-primary-500': isMemberOwner,
|
'bg-primary-200 text-primary-500': isMemberOwner,
|
||||||
|
|
|
@ -102,6 +102,7 @@ const GroupTagListItem = (props: IGroupMemberListItem) => {
|
||||||
require('@tabler/icons/pin.svg')
|
require('@tabler/icons/pin.svg')
|
||||||
}
|
}
|
||||||
iconClassName='h-5 w-5 text-primary-500 dark:text-accent-blue'
|
iconClassName='h-5 w-5 text-primary-500 dark:text-accent-blue'
|
||||||
|
data-testid='pin-icon'
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
|
@ -123,13 +124,18 @@ const GroupTagListItem = (props: IGroupMemberListItem) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HStack alignItems='center' justifyContent='between'>
|
<HStack
|
||||||
|
alignItems='center'
|
||||||
|
justifyContent='between'
|
||||||
|
data-testid='group-tag-list-item'
|
||||||
|
>
|
||||||
<Link to={`/group/${group.slug}/tag/${tag.id}`} className='group grow'>
|
<Link to={`/group/${group.slug}/tag/${tag.id}`} className='group grow'>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Text
|
<Text
|
||||||
weight='bold'
|
weight='bold'
|
||||||
theme={(tag.visible || !isOwner) ? 'default' : 'subtle'}
|
theme={(tag.visible || !isOwner) ? 'default' : 'subtle'}
|
||||||
className='group-hover:underline'
|
className='group-hover:underline'
|
||||||
|
data-testid='group-tag-name'
|
||||||
>
|
>
|
||||||
#{tag.name}
|
#{tag.name}
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -137,7 +143,7 @@ const GroupTagListItem = (props: IGroupMemberListItem) => {
|
||||||
{intl.formatMessage(messages.total)}:
|
{intl.formatMessage(messages.total)}:
|
||||||
{' '}
|
{' '}
|
||||||
<Text size='sm' theme='inherit' weight='semibold' tag='span'>
|
<Text size='sm' theme='inherit' weight='semibold' tag='span'>
|
||||||
{shortNumberFormat(tag.groups)}
|
{shortNumberFormat(tag.uses)}
|
||||||
</Text>
|
</Text>
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
|
@ -1,23 +1,34 @@
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
accountSchema,
|
||||||
adSchema,
|
adSchema,
|
||||||
cardSchema,
|
cardSchema,
|
||||||
groupSchema,
|
groupMemberSchema,
|
||||||
groupRelationshipSchema,
|
groupRelationshipSchema,
|
||||||
|
groupSchema,
|
||||||
groupTagSchema,
|
groupTagSchema,
|
||||||
relationshipSchema,
|
relationshipSchema,
|
||||||
|
type Account,
|
||||||
type Ad,
|
type Ad,
|
||||||
type Card,
|
type Card,
|
||||||
type Group,
|
type Group,
|
||||||
|
type GroupMember,
|
||||||
type GroupRelationship,
|
type GroupRelationship,
|
||||||
type GroupTag,
|
type GroupTag,
|
||||||
type Relationship,
|
type Relationship,
|
||||||
} from 'soapbox/schemas';
|
} from 'soapbox/schemas';
|
||||||
|
import { GroupRoles } from 'soapbox/schemas/group-member';
|
||||||
|
|
||||||
// TODO: there's probably a better way to create these factory functions.
|
// TODO: there's probably a better way to create these factory functions.
|
||||||
// This looks promising but didn't work on my first attempt: https://github.com/anatine/zod-plugins/tree/main/packages/zod-mock
|
// This looks promising but didn't work on my first attempt: https://github.com/anatine/zod-plugins/tree/main/packages/zod-mock
|
||||||
|
|
||||||
|
function buildAccount(props: Partial<Account> = {}): Account {
|
||||||
|
return accountSchema.parse(Object.assign({
|
||||||
|
id: uuidv4(),
|
||||||
|
}, props));
|
||||||
|
}
|
||||||
|
|
||||||
function buildCard(props: Partial<Card> = {}): Card {
|
function buildCard(props: Partial<Card> = {}): Card {
|
||||||
return cardSchema.parse(Object.assign({
|
return cardSchema.parse(Object.assign({
|
||||||
url: 'https://soapbox.test',
|
url: 'https://soapbox.test',
|
||||||
|
@ -39,6 +50,18 @@ function buildGroupRelationship(props: Partial<GroupRelationship> = {}): GroupRe
|
||||||
function buildGroupTag(props: Partial<GroupTag> = {}): GroupTag {
|
function buildGroupTag(props: Partial<GroupTag> = {}): GroupTag {
|
||||||
return groupTagSchema.parse(Object.assign({
|
return groupTagSchema.parse(Object.assign({
|
||||||
id: uuidv4(),
|
id: uuidv4(),
|
||||||
|
name: uuidv4(),
|
||||||
|
}, props));
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildGroupMember(
|
||||||
|
props: Partial<GroupMember> = {},
|
||||||
|
accountProps: Partial<Account> = {},
|
||||||
|
): GroupMember {
|
||||||
|
return groupMemberSchema.parse(Object.assign({
|
||||||
|
id: uuidv4(),
|
||||||
|
account: buildAccount(accountProps),
|
||||||
|
role: GroupRoles.USER,
|
||||||
}, props));
|
}, props));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,10 +78,11 @@ function buildRelationship(props: Partial<Relationship> = {}): Relationship {
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
buildAd,
|
||||||
buildCard,
|
buildCard,
|
||||||
buildGroup,
|
buildGroup,
|
||||||
|
buildGroupMember,
|
||||||
buildGroupRelationship,
|
buildGroupRelationship,
|
||||||
buildGroupTag,
|
buildGroupTag,
|
||||||
buildAd,
|
|
||||||
buildRelationship,
|
buildRelationship,
|
||||||
};
|
};
|
|
@ -123,17 +123,19 @@ const GroupPage: React.FC<IGroupPage> = ({ params, children }) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
items.push({
|
items.push(
|
||||||
text: intl.formatMessage(messages.members),
|
{
|
||||||
to: `/group/${group?.slug}/members`,
|
text: intl.formatMessage(messages.media),
|
||||||
name: '/group/:groupSlug/members',
|
to: `/group/${group?.slug}/media`,
|
||||||
count: pending.length,
|
name: '/group/:groupSlug/media',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: intl.formatMessage(messages.media),
|
text: intl.formatMessage(messages.members),
|
||||||
to: `/group/${group?.slug}/media`,
|
to: `/group/${group?.slug}/members`,
|
||||||
name: '/group/:groupSlug/media',
|
name: '/group/:groupSlug/members',
|
||||||
});
|
count: pending.length,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
}, [features.groupsTags, pending.length]);
|
}, [features.groupsTags, pending.length]);
|
||||||
|
|
|
@ -5,6 +5,7 @@ const groupTagSchema = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
groups: z.number().optional(),
|
groups: z.number().optional(),
|
||||||
url: z.string().optional(),
|
url: z.string().optional(),
|
||||||
|
uses: z.number().optional(),
|
||||||
pinned: z.boolean().optional().catch(false),
|
pinned: z.boolean().optional().catch(false),
|
||||||
visible: z.boolean().optional().default(true),
|
visible: z.boolean().optional().default(true),
|
||||||
});
|
});
|
||||||
|
|
Ładowanie…
Reference in New Issue