diff --git a/src/components/dialogs/GroupDialog/__tests__/useGroupMembers.test.js b/src/components/dialogs/GroupDialog/__tests__/useGroupMembers.test.js
index bc817a50..16ea01a9 100644
--- a/src/components/dialogs/GroupDialog/__tests__/useGroupMembers.test.js
+++ b/src/components/dialogs/GroupDialog/__tests__/useGroupMembers.test.js
@@ -80,12 +80,11 @@ vi.mock('../../../../services/request', () => ({
}));
vi.mock('vue-i18n', () => ({
useI18n: () => ({
- t: (key) => key
- ,
- locale: require('vue').ref('en')
- }),
+ t: (key) => key,
+ locale: require('vue').ref('en')
+ }),
createI18n: () => ({
- global: { t: (key) => key , locale: require('vue').ref('en') },
+ global: { t: (key) => key, locale: require('vue').ref('en') },
install: vi.fn()
})
}));
@@ -96,7 +95,7 @@ vi.mock('worker-timers', () => ({
import { useGroupMembers } from '../useGroupMembers';
import { groupRequest, queryRequest } from '../../../../api';
-import { groupDialogFilterOptions } from '../../../../shared/constants';
+import { FILTER_EVERYONE } from '../../../../shared/constants';
/**
*
@@ -409,9 +408,7 @@ describe('useGroupMembers', () => {
test('marks done on error', async () => {
const groupDialog = createGroupDialog();
- queryRequest.fetch.mockRejectedValue(
- new Error('fail')
- );
+ queryRequest.fetch.mockRejectedValue(new Error('fail'));
const {
loadMoreGroupMembers,
@@ -450,7 +447,7 @@ describe('useGroupMembers', () => {
describe('setGroupMemberFilter', () => {
test('does not reload when filter unchanged', async () => {
const { markRaw } = require('vue');
- const filter = markRaw(groupDialogFilterOptions.everyone);
+ const filter = markRaw(FILTER_EVERYONE);
const groupDialog = createGroupDialog();
// Use markRaw to prevent Vue from wrapping the filter in a Proxy
groupDialog.value.memberFilter = filter;
diff --git a/src/components/dialogs/GroupDialog/useGroupMembers.js b/src/components/dialogs/GroupDialog/useGroupMembers.js
index 6ce81045..f17a7dad 100644
--- a/src/components/dialogs/GroupDialog/useGroupMembers.js
+++ b/src/components/dialogs/GroupDialog/useGroupMembers.js
@@ -2,7 +2,9 @@ import { computed, ref } from 'vue';
import {
groupDialogFilterOptions,
- groupDialogSortingOptions
+ groupDialogSortingOptions,
+ FILTER_EVERYONE,
+ FILTER_NO_ROLE
} from '../../../shared/constants';
import { groupRequest, queryRequest } from '../../../api';
import { debounce } from '../../../shared/utils';
@@ -39,7 +41,7 @@ export function useGroupMembers(
return groupDialog.value?.memberSortOrder?.value ?? '';
},
set(value) {
- const option = Object.values(groupDialogSortingOptions).find(
+ const option = groupDialogSortingOptions.find(
(item) => item.value === value
);
if (option) {
@@ -61,11 +63,11 @@ export function useGroupMembers(
if (!key) return;
if (key === 'everyone') {
- setGroupMemberFilter(groupDialogFilterOptions.everyone);
+ setGroupMemberFilter(FILTER_EVERYONE);
return;
}
if (key === 'usersWithNoRole') {
- setGroupMemberFilter(groupDialogFilterOptions.usersWithNoRole);
+ setGroupMemberFilter(FILTER_NO_ROLE);
return;
}
@@ -82,18 +84,16 @@ export function useGroupMembers(
});
const groupDialogMemberFilterGroups = computed(() => {
- const filterItems = Object.values(groupDialogFilterOptions).map(
- (item) => ({
- value:
- item.id === null
- ? 'everyone'
- : item.id === ''
- ? 'usersWithNoRole'
- : `role:${item.id}`,
- label: t(item.name),
- search: t(item.name)
- })
- );
+ const filterItems = groupDialogFilterOptions.map((item) => ({
+ value:
+ item.id === null
+ ? 'everyone'
+ : item.id === ''
+ ? 'usersWithNoRole'
+ : `role:${item.id}`,
+ label: t(item.name),
+ search: t(item.name)
+ }));
const roleItems = (groupDialog.value?.ref?.roles ?? [])
.filter((role) => !role.defaultRole)
diff --git a/src/components/ui/dialog/DialogContent.vue b/src/components/ui/dialog/DialogContent.vue
index ca33d2f4..54d5cc45 100644
--- a/src/components/ui/dialog/DialogContent.vue
+++ b/src/components/ui/dialog/DialogContent.vue
@@ -1,5 +1,12 @@
+
+
diff --git a/src/views/Feed/__tests__/columns.test.js b/src/views/Feed/__tests__/columns.test.js
new file mode 100644
index 00000000..19734f79
--- /dev/null
+++ b/src/views/Feed/__tests__/columns.test.js
@@ -0,0 +1,121 @@
+import { beforeEach, describe, expect, test, vi } from 'vitest';
+
+vi.mock('../../../plugins/i18n', () => ({
+ i18n: {
+ global: {
+ t: (key) => key
+ }
+ }
+}));
+
+vi.mock('../../../stores', () => ({
+ useGalleryStore: () => ({
+ showFullscreenImageDialog: vi.fn()
+ })
+}));
+
+vi.mock('../../../coordinators/userCoordinator', () => ({
+ showUserDialog: vi.fn()
+}));
+
+vi.mock('../../../shared/utils', () => ({
+ formatDateFilter: (value) => value,
+ statusClass: (value) => value,
+ timeToText: (value) => value
+}));
+
+vi.mock('../../../components/AvatarInfo.vue', () => ({
+ default: 'AvatarInfo'
+}));
+
+vi.mock('../../../components/Location.vue', () => ({
+ default: 'Location'
+}));
+
+vi.mock('../../../components/ui/badge', () => ({
+ Badge: 'Badge'
+}));
+
+vi.mock('../../../components/ui/button', () => ({
+ Button: 'Button'
+}));
+
+vi.mock('../../../components/ui/tooltip', () => ({
+ Tooltip: 'Tooltip',
+ TooltipContent: 'TooltipContent',
+ TooltipTrigger: 'TooltipTrigger'
+}));
+
+vi.mock('lucide-vue-next', () => ({
+ ArrowDown: 'ArrowDown',
+ ArrowRight: 'ArrowRight',
+ ArrowUpDown: 'ArrowUpDown',
+ ChevronDown: 'ChevronDown',
+ ChevronRight: 'ChevronRight'
+}));
+
+import { columns } from '../columns.jsx';
+
+function createElement(type, props, ...children) {
+ return {
+ type,
+ props: props ?? {},
+ children: children.flat()
+ };
+}
+
+function findNode(node, predicate) {
+ if (!node) return null;
+ if (Array.isArray(node)) {
+ for (const item of node) {
+ const result = findNode(item, predicate);
+ if (result) return result;
+ }
+ return null;
+ }
+ if (predicate(node)) return node;
+ if (!node.children) return null;
+ return findNode(node.children, predicate);
+}
+
+describe('views/Feed/columns.jsx', () => {
+ beforeEach(() => {
+ globalThis.React = { createElement };
+ });
+
+ test('renders current bio text in detail cell', () => {
+ const detailCol = columns.find((c) => c.id === 'detail');
+ const row = {
+ original: {
+ type: 'Bio',
+ previousBio: 'hello\nold',
+ bio: 'hello\nnew'
+ }
+ };
+
+ const vnode = detailCol.cell({ row });
+
+ expect(vnode.type).toBe('span');
+ expect(vnode.children).toContain('hello\nnew');
+ expect(vnode.props.innerHTML).toBeUndefined();
+ });
+
+ test('keeps multiline bio diff in expanded row', () => {
+ const expanderCol = columns.find((c) => c.id === 'expander');
+ const expanded = expanderCol.meta.expandedRow({
+ row: {
+ original: {
+ type: 'Bio',
+ previousBio: 'line1\nline2',
+ bio: 'line1\nline3'
+ }
+ }
+ });
+ const preNode = findNode(expanded, (n) => n.type === 'pre');
+
+ expect(preNode).toBeTruthy();
+ expect(preNode.props.innerHTML).toContain('x-text-added');
+ expect(preNode.props.innerHTML).toContain('x-text-removed');
+ expect(preNode.props.innerHTML).toContain('
');
+ });
+});
diff --git a/src/views/Feed/columns.jsx b/src/views/Feed/columns.jsx
index 33439d46..34ca0d97 100644
--- a/src/views/Feed/columns.jsx
+++ b/src/views/Feed/columns.jsx
@@ -386,9 +386,9 @@ export const columns = [
if (type === 'Bio') {
return (
-