@axinom/mosaic-ui 0.65.3 → 0.65.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/List/ListRow/Renderers/TagsRenderer/TagsRenderer.d.ts.map +1 -1
- package/dist/components/Utils/Postgraphile/CreateConnectionRenderer.d.ts +1 -1
- package/dist/components/Utils/Postgraphile/CreateConnectionRenderer.d.ts.map +1 -1
- package/dist/index.es.js +3 -3
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/Explorer/Explorer.stories.tsx +17 -0
- package/src/components/List/ListRow/Renderers/TagsRenderer/TagsRenderer.spec.tsx +0 -28
- package/src/components/List/ListRow/Renderers/TagsRenderer/TagsRenderer.tsx +96 -71
- package/src/components/Utils/Postgraphile/CreateConnectionRenderer.spec.ts +26 -115
- package/src/components/Utils/Postgraphile/CreateConnectionRenderer.tsx +4 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axinom/mosaic-ui",
|
|
3
|
-
"version": "0.65.
|
|
3
|
+
"version": "0.65.5",
|
|
4
4
|
"description": "UI components for building Axinom Mosaic applications",
|
|
5
5
|
"author": "Axinom",
|
|
6
6
|
"license": "PROPRIETARY",
|
|
@@ -112,5 +112,5 @@
|
|
|
112
112
|
"publishConfig": {
|
|
113
113
|
"access": "public"
|
|
114
114
|
},
|
|
115
|
-
"gitHead": "
|
|
115
|
+
"gitHead": "2dccfe1c1492cbb54be6b6e9af3602e717cdce57"
|
|
116
116
|
}
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
} from '../FormStation';
|
|
22
22
|
import { IconName } from '../Icons';
|
|
23
23
|
import { ListSelectMode } from '../List';
|
|
24
|
+
import { createConnectionRenderer } from '../Utils';
|
|
24
25
|
import { generateBulkEditMutation } from './BulkEdit/GenerateMutation';
|
|
25
26
|
import { Explorer } from './Explorer';
|
|
26
27
|
import { QuickEditContext } from './QuickEdit/QuickEditContext';
|
|
@@ -34,6 +35,7 @@ interface ExplorerStoryData {
|
|
|
34
35
|
title: string;
|
|
35
36
|
date?: Date;
|
|
36
37
|
desc: string;
|
|
38
|
+
tags?: { nodes: { name: string }[] };
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
type ExplorerStoryType = typeof Explorer<ExplorerStoryData>;
|
|
@@ -120,6 +122,14 @@ const generateData = (
|
|
|
120
122
|
title: `${usePrefix ? `Index ${index}: ` : ''}${faker.random.words(
|
|
121
123
|
faker.datatype.number({ min: 1, max: 3 }),
|
|
122
124
|
)}`,
|
|
125
|
+
tags: {
|
|
126
|
+
nodes: Array.from(
|
|
127
|
+
{ length: faker.datatype.number({ min: 0, max: 10 }) },
|
|
128
|
+
() => ({
|
|
129
|
+
name: faker.random.word(),
|
|
130
|
+
}),
|
|
131
|
+
),
|
|
132
|
+
},
|
|
123
133
|
};
|
|
124
134
|
});
|
|
125
135
|
|
|
@@ -140,6 +150,13 @@ export const Default: StoryObj<ExplorerStoryType> = {
|
|
|
140
150
|
propertyName: 'title',
|
|
141
151
|
label: 'Title',
|
|
142
152
|
},
|
|
153
|
+
{
|
|
154
|
+
propertyName: 'tags',
|
|
155
|
+
label: 'Tags',
|
|
156
|
+
render: createConnectionRenderer<{ nodes: { name: string }[] }>(
|
|
157
|
+
(tag) => tag.name,
|
|
158
|
+
),
|
|
159
|
+
},
|
|
143
160
|
{
|
|
144
161
|
propertyName: 'date',
|
|
145
162
|
label: 'Date',
|
|
@@ -34,27 +34,6 @@ describe('TagsRenderer', () => {
|
|
|
34
34
|
expect(container.firstChild).toBeInTheDocument();
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
-
it('returns empty content when value is not an array', () => {
|
|
38
|
-
const { container } = render(
|
|
39
|
-
<RendererWrapper {...defaultProps} value="not-an-array" />,
|
|
40
|
-
);
|
|
41
|
-
expect(container.firstChild).toBeNull();
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('returns empty content when value is null', () => {
|
|
45
|
-
const { container } = render(
|
|
46
|
-
<RendererWrapper {...defaultProps} value={null} />,
|
|
47
|
-
);
|
|
48
|
-
expect(container.firstChild).toBeNull();
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('returns empty content when value is undefined', () => {
|
|
52
|
-
const { container } = render(
|
|
53
|
-
<RendererWrapper {...defaultProps} value={undefined} />,
|
|
54
|
-
);
|
|
55
|
-
expect(container.firstChild).toBeNull();
|
|
56
|
-
});
|
|
57
|
-
|
|
58
37
|
it('renders tags when array is provided', () => {
|
|
59
38
|
const mockTags = ['tag1', 'tag2', 'tag3'];
|
|
60
39
|
|
|
@@ -127,13 +106,6 @@ describe('TagsRenderer', () => {
|
|
|
127
106
|
expect(container).toBeInTheDocument();
|
|
128
107
|
});
|
|
129
108
|
|
|
130
|
-
it('handles empty array', () => {
|
|
131
|
-
const { container } = render(
|
|
132
|
-
<RendererWrapper {...defaultProps} value={[]} />,
|
|
133
|
-
);
|
|
134
|
-
expect(container.firstChild).toBeNull();
|
|
135
|
-
});
|
|
136
|
-
|
|
137
109
|
it('cleans up ResizeObserver on unmount', () => {
|
|
138
110
|
const mockDisconnect = jest.fn();
|
|
139
111
|
(global.ResizeObserver as jest.Mock).mockImplementation(() => ({
|
|
@@ -6,91 +6,120 @@ export const TagsRenderer = (val: unknown): JSX.Element => {
|
|
|
6
6
|
const resizeTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
7
7
|
const [visibleCount, setVisibleCount] = useState<number>(0);
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
const prevValRef = useRef<string[]>([]);
|
|
10
|
+
const valRef = useRef(val);
|
|
11
|
+
valRef.current = val; // Update on every render
|
|
12
|
+
|
|
13
|
+
// Shared calculation function that uses valRef for current value
|
|
14
|
+
const calculateVisibleItems = (): void => {
|
|
15
|
+
const currentVal = valRef.current;
|
|
16
|
+
const container = containerRef.current;
|
|
17
|
+
|
|
18
|
+
if (!container || !Array.isArray(currentVal) || currentVal.length === 0) {
|
|
12
19
|
return;
|
|
13
20
|
}
|
|
14
21
|
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
22
|
+
const containerWidth = container.offsetWidth;
|
|
23
|
+
const gap = 5; // Gap between items from SCSS
|
|
24
|
+
const overflowIndicatorWidth = 60; // Approximate width for "... +X"
|
|
25
|
+
|
|
26
|
+
// Measure actual tag widths using hidden temporary elements in the actual container
|
|
27
|
+
const tagWidths: number[] = [];
|
|
28
|
+
const tempElements: HTMLDivElement[] = [];
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
(currentVal as string[]).forEach((item) => {
|
|
32
|
+
const tempTag = document.createElement('div');
|
|
33
|
+
tempTag.className = classes.tag;
|
|
34
|
+
tempTag.textContent = item;
|
|
35
|
+
tempTag.style.position = 'absolute';
|
|
36
|
+
tempTag.style.visibility = 'hidden';
|
|
37
|
+
tempTag.style.pointerEvents = 'none';
|
|
38
|
+
container.appendChild(tempTag);
|
|
39
|
+
tempElements.push(tempTag);
|
|
40
|
+
tagWidths.push(tempTag.offsetWidth);
|
|
41
|
+
});
|
|
42
|
+
} finally {
|
|
43
|
+
// Clean up temporary elements
|
|
44
|
+
tempElements.forEach((el) => container.removeChild(el));
|
|
45
|
+
}
|
|
20
46
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const tagWidths
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
tempElements.push(tempTag);
|
|
39
|
-
tagWidths.push(tempTag.offsetWidth);
|
|
40
|
-
});
|
|
41
|
-
} finally {
|
|
42
|
-
// Clean up temporary elements
|
|
43
|
-
tempElements.forEach((el) => container.removeChild(el));
|
|
47
|
+
// Calculate how many items can fit
|
|
48
|
+
let totalWidth = 0;
|
|
49
|
+
let newVisibleCount = 0;
|
|
50
|
+
|
|
51
|
+
for (let i = 0; i < (currentVal as string[]).length; i++) {
|
|
52
|
+
const itemWidth = tagWidths[i] + (i > 0 ? gap : 0);
|
|
53
|
+
const wouldNeedOverflow = i < (currentVal as string[]).length - 1;
|
|
54
|
+
const requiredWidth =
|
|
55
|
+
totalWidth +
|
|
56
|
+
itemWidth +
|
|
57
|
+
(wouldNeedOverflow ? overflowIndicatorWidth + gap : 0);
|
|
58
|
+
|
|
59
|
+
if (requiredWidth <= containerWidth) {
|
|
60
|
+
totalWidth += itemWidth;
|
|
61
|
+
newVisibleCount++;
|
|
62
|
+
} else {
|
|
63
|
+
break;
|
|
44
64
|
}
|
|
65
|
+
}
|
|
45
66
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
67
|
+
// If we can't fit all items, reserve space for overflow indicator
|
|
68
|
+
if (
|
|
69
|
+
newVisibleCount < (currentVal as string[]).length &&
|
|
70
|
+
newVisibleCount > 0
|
|
71
|
+
) {
|
|
72
|
+
// Re-calculate with overflow indicator space reserved
|
|
73
|
+
totalWidth = 0;
|
|
74
|
+
newVisibleCount = 0;
|
|
49
75
|
|
|
50
|
-
for (let i = 0; i <
|
|
76
|
+
for (let i = 0; i < (currentVal as string[]).length; i++) {
|
|
51
77
|
const itemWidth = tagWidths[i] + (i > 0 ? gap : 0);
|
|
52
|
-
const wouldNeedOverflow = i < val.length - 1;
|
|
53
78
|
const requiredWidth =
|
|
54
|
-
totalWidth +
|
|
55
|
-
itemWidth +
|
|
56
|
-
(wouldNeedOverflow ? overflowIndicatorWidth + gap : 0);
|
|
79
|
+
totalWidth + itemWidth + overflowIndicatorWidth + gap;
|
|
57
80
|
|
|
58
|
-
if (
|
|
81
|
+
if (
|
|
82
|
+
requiredWidth <= containerWidth &&
|
|
83
|
+
i < (currentVal as string[]).length - 1
|
|
84
|
+
) {
|
|
59
85
|
totalWidth += itemWidth;
|
|
60
86
|
newVisibleCount++;
|
|
61
87
|
} else {
|
|
62
88
|
break;
|
|
63
89
|
}
|
|
64
90
|
}
|
|
91
|
+
}
|
|
65
92
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
totalWidth = 0;
|
|
70
|
-
newVisibleCount = 0;
|
|
71
|
-
|
|
72
|
-
for (let i = 0; i < val.length; i++) {
|
|
73
|
-
const itemWidth = tagWidths[i] + (i > 0 ? gap : 0);
|
|
74
|
-
const requiredWidth =
|
|
75
|
-
totalWidth + itemWidth + overflowIndicatorWidth + gap;
|
|
76
|
-
|
|
77
|
-
if (requiredWidth <= containerWidth && i < val.length - 1) {
|
|
78
|
-
totalWidth += itemWidth;
|
|
79
|
-
newVisibleCount++;
|
|
80
|
-
} else {
|
|
81
|
-
break;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
93
|
+
// Update state with the new visible count
|
|
94
|
+
setVisibleCount(newVisibleCount);
|
|
95
|
+
};
|
|
85
96
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
97
|
+
// Effect 1: Handle tag changes (runs when val changes)
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
if (!Array.isArray(val) || val.length === 0) {
|
|
100
|
+
setVisibleCount(0);
|
|
101
|
+
prevValRef.current = [];
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Check if array contents actually changed
|
|
106
|
+
const valArray = val as string[];
|
|
107
|
+
const hasChanged =
|
|
108
|
+
valArray.length !== prevValRef.current.length ||
|
|
109
|
+
valArray.some((item, index) => item !== prevValRef.current[index]);
|
|
110
|
+
|
|
111
|
+
if (hasChanged) {
|
|
112
|
+
calculateVisibleItems();
|
|
113
|
+
prevValRef.current = [...valArray];
|
|
114
|
+
}
|
|
115
|
+
}, [val]);
|
|
89
116
|
|
|
90
|
-
|
|
91
|
-
|
|
117
|
+
// Effect 2: Set up ResizeObserver (runs once on mount)
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
if (!containerRef.current) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
92
122
|
|
|
93
|
-
// Debounced resize handler
|
|
94
123
|
const debouncedCalculate = (): void => {
|
|
95
124
|
if (resizeTimeoutRef.current) {
|
|
96
125
|
clearTimeout(resizeTimeoutRef.current);
|
|
@@ -109,13 +138,9 @@ export const TagsRenderer = (val: unknown): JSX.Element => {
|
|
|
109
138
|
}
|
|
110
139
|
resizeObserver.disconnect();
|
|
111
140
|
};
|
|
112
|
-
}, [
|
|
113
|
-
|
|
114
|
-
if (!Array.isArray(val) || val.length === 0) {
|
|
115
|
-
return <></>;
|
|
116
|
-
}
|
|
141
|
+
}, []); // Empty deps - only runs once
|
|
117
142
|
|
|
118
|
-
const stringArray = val as string[];
|
|
143
|
+
const stringArray = Array.isArray(val) ? (val as string[]) : [];
|
|
119
144
|
const visibleItems = stringArray.slice(0, visibleCount);
|
|
120
145
|
const hiddenItems = stringArray.slice(visibleCount);
|
|
121
146
|
|
|
@@ -1,14 +1,5 @@
|
|
|
1
|
-
import { TagsRenderer } from '../../List';
|
|
2
1
|
import { createConnectionRenderer } from './CreateConnectionRenderer';
|
|
3
2
|
|
|
4
|
-
jest.mock('../../List', () => ({
|
|
5
|
-
TagsRenderer: jest.fn(),
|
|
6
|
-
}));
|
|
7
|
-
|
|
8
|
-
const mockTagsRenderer = TagsRenderer as jest.MockedFunction<
|
|
9
|
-
typeof TagsRenderer
|
|
10
|
-
>;
|
|
11
|
-
|
|
12
3
|
interface TestConnection {
|
|
13
4
|
nodes: { id: string; name: string }[];
|
|
14
5
|
}
|
|
@@ -18,70 +9,7 @@ interface StringConnection {
|
|
|
18
9
|
}
|
|
19
10
|
|
|
20
11
|
describe('createConnectionRenderer', () => {
|
|
21
|
-
|
|
22
|
-
jest.clearAllMocks();
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
describe('with renderAsTags = true (default)', () => {
|
|
26
|
-
it('should call TagsRenderer with mapped values when renderAsTags is true', () => {
|
|
27
|
-
const mockReturnValue = 'Mocked Tags JSX Element';
|
|
28
|
-
mockTagsRenderer.mockReturnValue(mockReturnValue as any);
|
|
29
|
-
|
|
30
|
-
const connection: TestConnection = {
|
|
31
|
-
nodes: [
|
|
32
|
-
{ id: '1', name: 'Item 1' },
|
|
33
|
-
{ id: '2', name: 'Item 2' },
|
|
34
|
-
],
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const selector = (item: { id: string; name: string }) => item.name;
|
|
38
|
-
const renderer = createConnectionRenderer<TestConnection>(selector);
|
|
39
|
-
const result = renderer(connection);
|
|
40
|
-
|
|
41
|
-
expect(mockTagsRenderer).toHaveBeenCalledWith(['Item 1', 'Item 2']);
|
|
42
|
-
expect(result).toBe(mockReturnValue);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('should throw error when nodes is undefined', () => {
|
|
46
|
-
const connection = { nodes: undefined } as unknown as TestConnection;
|
|
47
|
-
const selector = (item: { id: string; name: string }) => item.name;
|
|
48
|
-
const renderer = createConnectionRenderer<TestConnection>(selector);
|
|
49
|
-
|
|
50
|
-
expect(() => renderer(connection)).toThrow();
|
|
51
|
-
expect(mockTagsRenderer).not.toHaveBeenCalled();
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('should throw error when value is null', () => {
|
|
55
|
-
const selector = (item: { id: string; name: string }) => item.name;
|
|
56
|
-
const renderer = createConnectionRenderer<TestConnection>(selector);
|
|
57
|
-
|
|
58
|
-
expect(() => renderer(null)).toThrow();
|
|
59
|
-
expect(mockTagsRenderer).not.toHaveBeenCalled();
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('should throw error when value is undefined', () => {
|
|
63
|
-
const selector = (item: { id: string; name: string }) => item.name;
|
|
64
|
-
const renderer = createConnectionRenderer<TestConnection>(selector);
|
|
65
|
-
|
|
66
|
-
expect(() => renderer(undefined)).toThrow();
|
|
67
|
-
expect(mockTagsRenderer).not.toHaveBeenCalled();
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('should call TagsRenderer with empty array when nodes is empty', () => {
|
|
71
|
-
const mockReturnValue = 'Empty Tags JSX Element';
|
|
72
|
-
mockTagsRenderer.mockReturnValue(mockReturnValue as any);
|
|
73
|
-
|
|
74
|
-
const connection: TestConnection = { nodes: [] };
|
|
75
|
-
const selector = (item: { id: string; name: string }) => item.name;
|
|
76
|
-
const renderer = createConnectionRenderer<TestConnection>(selector);
|
|
77
|
-
const result = renderer(connection);
|
|
78
|
-
|
|
79
|
-
expect(mockTagsRenderer).toHaveBeenCalledWith([]);
|
|
80
|
-
expect(result).toBe(mockReturnValue);
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
describe('with renderAsTags = false', () => {
|
|
12
|
+
describe('basic functionality', () => {
|
|
85
13
|
it('should return comma-separated string using selector', () => {
|
|
86
14
|
const connection: TestConnection = {
|
|
87
15
|
nodes: [
|
|
@@ -92,13 +20,9 @@ describe('createConnectionRenderer', () => {
|
|
|
92
20
|
};
|
|
93
21
|
|
|
94
22
|
const selector = (item: { id: string; name: string }) => item.name;
|
|
95
|
-
const renderer = createConnectionRenderer<TestConnection>(
|
|
96
|
-
selector,
|
|
97
|
-
false,
|
|
98
|
-
);
|
|
23
|
+
const renderer = createConnectionRenderer<TestConnection>(selector);
|
|
99
24
|
const result = renderer(connection);
|
|
100
25
|
|
|
101
|
-
expect(mockTagsRenderer).not.toHaveBeenCalled();
|
|
102
26
|
expect(result).toBe('Item 1, Item 2, Item 3');
|
|
103
27
|
});
|
|
104
28
|
|
|
@@ -108,10 +32,7 @@ describe('createConnectionRenderer', () => {
|
|
|
108
32
|
};
|
|
109
33
|
|
|
110
34
|
const selector = (item: string) => item;
|
|
111
|
-
const renderer = createConnectionRenderer<StringConnection>(
|
|
112
|
-
selector,
|
|
113
|
-
false,
|
|
114
|
-
);
|
|
35
|
+
const renderer = createConnectionRenderer<StringConnection>(selector);
|
|
115
36
|
const result = renderer(connection);
|
|
116
37
|
|
|
117
38
|
expect(result).toBe('apple, banana, cherry');
|
|
@@ -127,10 +48,7 @@ describe('createConnectionRenderer', () => {
|
|
|
127
48
|
|
|
128
49
|
const selector = (item: { id: string; name: string }, index: number) =>
|
|
129
50
|
`${index + 1}. ${item.name} (${item.id})`;
|
|
130
|
-
const renderer = createConnectionRenderer<TestConnection>(
|
|
131
|
-
selector,
|
|
132
|
-
false,
|
|
133
|
-
);
|
|
51
|
+
const renderer = createConnectionRenderer<TestConnection>(selector);
|
|
134
52
|
const result = renderer(connection);
|
|
135
53
|
|
|
136
54
|
expect(result).toBe('1. Item 1 (1), 2. Item 2 (2)');
|
|
@@ -139,10 +57,7 @@ describe('createConnectionRenderer', () => {
|
|
|
139
57
|
it('should return empty string when nodes array is empty', () => {
|
|
140
58
|
const connection: TestConnection = { nodes: [] };
|
|
141
59
|
const selector = (item: { id: string; name: string }) => item.name;
|
|
142
|
-
const renderer = createConnectionRenderer<TestConnection>(
|
|
143
|
-
selector,
|
|
144
|
-
false,
|
|
145
|
-
);
|
|
60
|
+
const renderer = createConnectionRenderer<TestConnection>(selector);
|
|
146
61
|
const result = renderer(connection);
|
|
147
62
|
|
|
148
63
|
expect(result).toBe('');
|
|
@@ -160,25 +75,33 @@ describe('createConnectionRenderer', () => {
|
|
|
160
75
|
};
|
|
161
76
|
|
|
162
77
|
const selector = (item: { id: number; value: number }) => item.value;
|
|
163
|
-
const renderer = createConnectionRenderer<NumberConnection>(
|
|
164
|
-
selector,
|
|
165
|
-
false,
|
|
166
|
-
);
|
|
78
|
+
const renderer = createConnectionRenderer<NumberConnection>(selector);
|
|
167
79
|
const result = renderer(connection);
|
|
168
80
|
|
|
169
81
|
expect(result).toBe('100, 200');
|
|
170
82
|
});
|
|
171
83
|
|
|
172
|
-
it('should throw error when connection.nodes is undefined
|
|
84
|
+
it('should throw error when connection.nodes is undefined', () => {
|
|
173
85
|
const connection = { nodes: undefined } as unknown as TestConnection;
|
|
174
86
|
const selector = (item: { id: string; name: string }) => item.name;
|
|
175
|
-
const renderer = createConnectionRenderer<TestConnection>(
|
|
176
|
-
selector,
|
|
177
|
-
false,
|
|
178
|
-
);
|
|
87
|
+
const renderer = createConnectionRenderer<TestConnection>(selector);
|
|
179
88
|
|
|
180
89
|
expect(() => renderer(connection)).toThrow();
|
|
181
90
|
});
|
|
91
|
+
|
|
92
|
+
it('should throw error when value is null', () => {
|
|
93
|
+
const selector = (item: { id: string; name: string }) => item.name;
|
|
94
|
+
const renderer = createConnectionRenderer<TestConnection>(selector);
|
|
95
|
+
|
|
96
|
+
expect(() => renderer(null)).toThrow();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should throw error when value is undefined', () => {
|
|
100
|
+
const selector = (item: { id: string; name: string }) => item.name;
|
|
101
|
+
const renderer = createConnectionRenderer<TestConnection>(selector);
|
|
102
|
+
|
|
103
|
+
expect(() => renderer(undefined)).toThrow();
|
|
104
|
+
});
|
|
182
105
|
});
|
|
183
106
|
|
|
184
107
|
describe('edge cases', () => {
|
|
@@ -188,10 +111,7 @@ describe('createConnectionRenderer', () => {
|
|
|
188
111
|
};
|
|
189
112
|
|
|
190
113
|
const selector = (item: string) => item;
|
|
191
|
-
const renderer = createConnectionRenderer<StringConnection>(
|
|
192
|
-
selector,
|
|
193
|
-
false,
|
|
194
|
-
);
|
|
114
|
+
const renderer = createConnectionRenderer<StringConnection>(selector);
|
|
195
115
|
const result = renderer(connection);
|
|
196
116
|
|
|
197
117
|
expect(result).toBe('item, with comma, item with "quotes", normal item');
|
|
@@ -207,10 +127,7 @@ describe('createConnectionRenderer', () => {
|
|
|
207
127
|
};
|
|
208
128
|
|
|
209
129
|
const selector = (item: { id: string; name: string }) => item.name;
|
|
210
|
-
const renderer = createConnectionRenderer<TestConnection>(
|
|
211
|
-
selector,
|
|
212
|
-
false,
|
|
213
|
-
);
|
|
130
|
+
const renderer = createConnectionRenderer<TestConnection>(selector);
|
|
214
131
|
const result = renderer(connection);
|
|
215
132
|
|
|
216
133
|
expect(result).toBe(', Item 2, ');
|
|
@@ -222,10 +139,7 @@ describe('createConnectionRenderer', () => {
|
|
|
222
139
|
};
|
|
223
140
|
|
|
224
141
|
const selector = (item: { id: string; name: string }) => item.name;
|
|
225
|
-
const renderer = createConnectionRenderer<TestConnection>(
|
|
226
|
-
selector,
|
|
227
|
-
false,
|
|
228
|
-
);
|
|
142
|
+
const renderer = createConnectionRenderer<TestConnection>(selector);
|
|
229
143
|
const result = renderer(connection);
|
|
230
144
|
|
|
231
145
|
expect(result).toBe('Single Item');
|
|
@@ -247,10 +161,7 @@ describe('createConnectionRenderer', () => {
|
|
|
247
161
|
|
|
248
162
|
const selector = (item: { customField: number; label: string }) =>
|
|
249
163
|
`${item.label}: ${item.customField}`;
|
|
250
|
-
const renderer = createConnectionRenderer<CustomConnection>(
|
|
251
|
-
selector,
|
|
252
|
-
false,
|
|
253
|
-
);
|
|
164
|
+
const renderer = createConnectionRenderer<CustomConnection>(selector);
|
|
254
165
|
const result = renderer(connection);
|
|
255
166
|
|
|
256
167
|
expect(result).toBe('Custom 1: 42, Custom 2: 84');
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
|
-
import { TagsRenderer } from '../../List';
|
|
3
2
|
|
|
4
3
|
interface Connection {
|
|
5
4
|
nodes: unknown[];
|
|
@@ -19,14 +18,14 @@ export type SelectorFunction<T> = (
|
|
|
19
18
|
*/
|
|
20
19
|
export function createConnectionRenderer<T extends Connection>(
|
|
21
20
|
selector: SelectorFunction<T['nodes'][number]>,
|
|
22
|
-
renderAsTags = true,
|
|
21
|
+
// renderAsTags = true,
|
|
23
22
|
): (val: unknown) => string | ReactNode {
|
|
24
23
|
const ConnectionRenderer = (val: unknown): string | ReactNode => {
|
|
25
24
|
const value = val as T;
|
|
26
25
|
|
|
27
|
-
if (renderAsTags) {
|
|
28
|
-
|
|
29
|
-
}
|
|
26
|
+
// if (renderAsTags) {
|
|
27
|
+
// return TagsRenderer(value.nodes.map(selector));
|
|
28
|
+
// }
|
|
30
29
|
|
|
31
30
|
return value.nodes.map(selector).join(', ');
|
|
32
31
|
};
|