Skip to content

Commit dd49aea

Browse files
authored
Fix database condition selector observers (#346)
1 parent 59c31c5 commit dd49aea

2 files changed

Lines changed: 312 additions & 63 deletions

File tree

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
import { act, renderHook, waitFor } from '@testing-library/react';
2+
import type React from 'react';
3+
import * as Y from 'yjs';
4+
5+
import {
6+
DatabaseContext,
7+
DatabaseContextState,
8+
FieldType,
9+
FilterType,
10+
SortCondition,
11+
TextFilterCondition,
12+
useFiltersSelector,
13+
useFilterSelector,
14+
useSortsSelector,
15+
useSortSelector,
16+
} from '@/application/database-yjs';
17+
import {
18+
RowId,
19+
YDatabaseField,
20+
YDatabaseFilter,
21+
YDatabaseFilters,
22+
YDatabaseSort,
23+
YDatabaseSorts,
24+
YDatabaseView,
25+
YDoc,
26+
YjsDatabaseKey,
27+
YjsEditorKey,
28+
} from '@/application/types';
29+
30+
type ConditionFixture = {
31+
databaseDoc: YDoc;
32+
fields: Y.Map<YDatabaseField>;
33+
view: YDatabaseView;
34+
viewId: string;
35+
};
36+
37+
const firstFieldId = 'first-field';
38+
const secondFieldId = 'second-field';
39+
40+
function createTextField(fieldId: string) {
41+
const field = new Y.Map() as YDatabaseField;
42+
43+
field.set(YjsDatabaseKey.id, fieldId);
44+
field.set(YjsDatabaseKey.name, fieldId);
45+
field.set(YjsDatabaseKey.type, FieldType.RichText);
46+
47+
return field;
48+
}
49+
50+
function createTextFilter(id: string, fieldId: string) {
51+
const filter = new Y.Map() as YDatabaseFilter;
52+
53+
filter.set(YjsDatabaseKey.id, id);
54+
filter.set(YjsDatabaseKey.field_id, fieldId);
55+
filter.set(YjsDatabaseKey.filter_type, FilterType.Data);
56+
filter.set(YjsDatabaseKey.condition, TextFilterCondition.TextContains);
57+
filter.set(YjsDatabaseKey.content, 'match');
58+
filter.set(YjsDatabaseKey.type, FieldType.RichText);
59+
60+
return filter;
61+
}
62+
63+
function createSort(id: string, fieldId: string) {
64+
const sort = new Y.Map() as YDatabaseSort;
65+
66+
sort.set(YjsDatabaseKey.id, id);
67+
sort.set(YjsDatabaseKey.field_id, fieldId);
68+
sort.set(YjsDatabaseKey.condition, SortCondition.Ascending);
69+
70+
return sort;
71+
}
72+
73+
function createConditionFixture(): ConditionFixture {
74+
const viewId = 'view-id';
75+
const databaseDoc = new Y.Doc() as unknown as YDoc;
76+
const sharedRoot = databaseDoc.getMap(YjsEditorKey.data_section);
77+
const database = new Y.Map();
78+
const fields = new Y.Map<YDatabaseField>();
79+
const views = new Y.Map();
80+
const view = new Y.Map() as YDatabaseView;
81+
82+
fields.set(firstFieldId, createTextField(firstFieldId));
83+
fields.set(secondFieldId, createTextField(secondFieldId));
84+
views.set(viewId, view);
85+
86+
database.set(YjsDatabaseKey.id, 'database-id');
87+
database.set(YjsDatabaseKey.fields, fields);
88+
database.set(YjsDatabaseKey.views, views);
89+
sharedRoot.set(YjsEditorKey.database, database);
90+
91+
return {
92+
databaseDoc,
93+
fields,
94+
view,
95+
viewId,
96+
};
97+
}
98+
99+
function createWrapper(fixture: ConditionFixture, contextOverrides: Partial<DatabaseContextState> = {}) {
100+
const contextValue: DatabaseContextState = {
101+
readOnly: false,
102+
databaseDoc: fixture.databaseDoc,
103+
databasePageId: fixture.viewId,
104+
activeViewId: fixture.viewId,
105+
rowMap: {} as Record<RowId, YDoc>,
106+
workspaceId: 'workspace-id',
107+
...contextOverrides,
108+
};
109+
110+
return ({ children }: { children: React.ReactNode }) => (
111+
<DatabaseContext.Provider value={contextValue}>{children}</DatabaseContext.Provider>
112+
);
113+
}
114+
115+
describe('database condition selectors', () => {
116+
it('observes a filters array created after mount', async () => {
117+
const fixture = createConditionFixture();
118+
const { result } = renderHook(() => useFiltersSelector(), {
119+
wrapper: createWrapper(fixture),
120+
});
121+
122+
expect(result.current).toEqual([]);
123+
124+
const filters = new Y.Array<YDatabaseFilter>() as YDatabaseFilters;
125+
126+
act(() => {
127+
fixture.view.set(YjsDatabaseKey.filters, filters);
128+
filters.push([createTextFilter('filter-id', firstFieldId)]);
129+
});
130+
131+
await waitFor(() => {
132+
expect(result.current).toEqual([{ id: 'filter-id', fieldId: firstFieldId }]);
133+
});
134+
});
135+
136+
it('updates filter selectors when the filter field changes', async () => {
137+
const fixture = createConditionFixture();
138+
const filter = createTextFilter('filter-id', firstFieldId);
139+
const filters = new Y.Array<YDatabaseFilter>() as YDatabaseFilters;
140+
141+
filters.push([filter]);
142+
fixture.view.set(YjsDatabaseKey.filters, filters);
143+
144+
const { result: filterListResult } = renderHook(() => useFiltersSelector(), {
145+
wrapper: createWrapper(fixture),
146+
});
147+
const { result: filterResult } = renderHook(() => useFilterSelector('filter-id'), {
148+
wrapper: createWrapper(fixture),
149+
});
150+
151+
await waitFor(() => {
152+
expect(filterListResult.current).toEqual([{ id: 'filter-id', fieldId: firstFieldId }]);
153+
expect(filterResult.current?.fieldId).toBe(firstFieldId);
154+
});
155+
156+
act(() => {
157+
filter.set(YjsDatabaseKey.field_id, secondFieldId);
158+
});
159+
160+
await waitFor(() => {
161+
expect(filterListResult.current).toEqual([{ id: 'filter-id', fieldId: secondFieldId }]);
162+
expect(filterResult.current?.fieldId).toBe(secondFieldId);
163+
});
164+
});
165+
166+
it('observes a sorts array created after mount', async () => {
167+
const fixture = createConditionFixture();
168+
const { result } = renderHook(() => useSortsSelector(), {
169+
wrapper: createWrapper(fixture),
170+
});
171+
172+
expect(result.current).toEqual([]);
173+
174+
const sorts = new Y.Array<YDatabaseSort>() as YDatabaseSorts;
175+
176+
act(() => {
177+
fixture.view.set(YjsDatabaseKey.sorts, sorts);
178+
sorts.push([createSort('sort-id', firstFieldId)]);
179+
});
180+
181+
await waitFor(() => {
182+
expect(result.current).toEqual([{ id: 'sort-id', fieldId: firstFieldId }]);
183+
});
184+
});
185+
186+
it('updates sort selectors when the sort field changes', async () => {
187+
const fixture = createConditionFixture();
188+
const sort = createSort('sort-id', firstFieldId);
189+
const sorts = new Y.Array<YDatabaseSort>() as YDatabaseSorts;
190+
191+
sorts.push([sort]);
192+
fixture.view.set(YjsDatabaseKey.sorts, sorts);
193+
194+
const { result: sortListResult } = renderHook(() => useSortsSelector(), {
195+
wrapper: createWrapper(fixture),
196+
});
197+
const { result: sortResult } = renderHook(() => useSortSelector('sort-id'), {
198+
wrapper: createWrapper(fixture),
199+
});
200+
201+
await waitFor(() => {
202+
expect(sortListResult.current).toEqual([{ id: 'sort-id', fieldId: firstFieldId }]);
203+
expect(sortResult.current?.fieldId).toBe(firstFieldId);
204+
});
205+
206+
act(() => {
207+
sort.set(YjsDatabaseKey.field_id, secondFieldId);
208+
});
209+
210+
await waitFor(() => {
211+
expect(sortListResult.current).toEqual([{ id: 'sort-id', fieldId: secondFieldId }]);
212+
expect(sortResult.current?.fieldId).toBe(secondFieldId);
213+
});
214+
});
215+
});

0 commit comments

Comments
 (0)