This commit is contained in:
pa
2026-03-10 23:09:04 +09:00
parent 607e09d271
commit 1c9e4621f5
10 changed files with 216 additions and 49 deletions
+17
View File
@@ -243,3 +243,20 @@
feedTableLookup();
}
</script>
<style scoped>
.feed :deep(.x-text-removed) {
text-decoration: line-through;
color: #ff0000;
background-color: rgba(255, 0, 0, 0.2);
padding: 2px 2px;
border-radius: 4px;
}
.feed :deep(.x-text-added) {
color: rgb(35, 188, 35);
background-color: rgba(76, 255, 80, 0.2);
padding: 2px 2px;
border-radius: 4px;
}
</style>
+121
View File
@@ -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('<br>');
});
});
+3 -3
View File
@@ -386,9 +386,9 @@ export const columns = [
if (type === 'Bio') {
return (
<div class="block w-full min-w-0 truncate">
<span class="block w-full min-w-0 truncate">
{original.bio}
</div>
</span>
);
}
@@ -415,7 +415,7 @@ function formatDifference(
markerDeletion = '<span class="x-text-removed">{{text}}</span>'
) {
[oldString, newString] = [oldString, newString].map((s) =>
s
String(s ?? '')
.replaceAll(/&/g, '&amp;')
.replaceAll(/</g, '&lt;')
.replaceAll(/>/g, '&gt;')