@livestore/react 0.2.0 → 0.3.0-dev.11
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/.tsbuildinfo +1 -1
- package/dist/LiveStoreContext.d.ts +5 -3
- package/dist/LiveStoreContext.d.ts.map +1 -1
- package/dist/LiveStoreContext.js +7 -3
- package/dist/LiveStoreContext.js.map +1 -1
- package/dist/LiveStoreProvider.d.ts +6 -4
- package/dist/LiveStoreProvider.d.ts.map +1 -1
- package/dist/LiveStoreProvider.js +47 -45
- package/dist/LiveStoreProvider.js.map +1 -1
- package/dist/LiveStoreProvider.test.js +8 -2
- package/dist/LiveStoreProvider.test.js.map +1 -1
- package/dist/__tests__/fixture.d.ts +7 -10
- package/dist/__tests__/fixture.d.ts.map +1 -1
- package/dist/__tests__/fixture.js +10 -15
- package/dist/__tests__/fixture.js.map +1 -1
- package/dist/experimental/components/LiveList.d.ts +2 -2
- package/dist/experimental/components/LiveList.d.ts.map +1 -1
- package/dist/experimental/components/LiveList.js +5 -4
- package/dist/experimental/components/LiveList.js.map +1 -1
- package/dist/mod.d.ts +0 -1
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +0 -1
- package/dist/mod.js.map +1 -1
- package/dist/useAtom.d.ts +4 -2
- package/dist/useAtom.d.ts.map +1 -1
- package/dist/useAtom.js +32 -28
- package/dist/useAtom.js.map +1 -1
- package/dist/useQuery.d.ts +26 -3
- package/dist/useQuery.d.ts.map +1 -1
- package/dist/useQuery.js +60 -45
- package/dist/useQuery.js.map +1 -1
- package/dist/useQuery.test.js +70 -16
- package/dist/useQuery.test.js.map +1 -1
- package/dist/useRcRef.d.ts +72 -0
- package/dist/useRcRef.d.ts.map +1 -0
- package/dist/useRcRef.js +146 -0
- package/dist/useRcRef.js.map +1 -0
- package/dist/useRcRef.test.d.ts +2 -0
- package/dist/useRcRef.test.d.ts.map +1 -0
- package/dist/useRcRef.test.js +128 -0
- package/dist/useRcRef.test.js.map +1 -0
- package/dist/useRcResource.d.ts +76 -0
- package/dist/useRcResource.d.ts.map +1 -0
- package/dist/useRcResource.js +150 -0
- package/dist/useRcResource.js.map +1 -0
- package/dist/useRcResource.test.d.ts +2 -0
- package/dist/useRcResource.test.d.ts.map +1 -0
- package/dist/useRcResource.test.js +122 -0
- package/dist/useRcResource.test.js.map +1 -0
- package/dist/useRow.d.ts +10 -7
- package/dist/useRow.d.ts.map +1 -1
- package/dist/useRow.js +16 -19
- package/dist/useRow.js.map +1 -1
- package/dist/useRow.test.js +74 -97
- package/dist/useRow.test.js.map +1 -1
- package/dist/useScopedQuery.d.ts +10 -4
- package/dist/useScopedQuery.d.ts.map +1 -1
- package/dist/useScopedQuery.js +97 -52
- package/dist/useScopedQuery.js.map +1 -1
- package/dist/useScopedQuery.test.js +13 -12
- package/dist/useScopedQuery.test.js.map +1 -1
- package/dist/utils/useStateRefWithReactiveInput.d.ts +1 -1
- package/dist/utils/useStateRefWithReactiveInput.d.ts.map +1 -1
- package/dist/utils/useStateRefWithReactiveInput.js.map +1 -1
- package/package.json +18 -17
- package/src/LiveStoreContext.ts +10 -6
- package/src/LiveStoreProvider.test.tsx +13 -2
- package/src/LiveStoreProvider.tsx +69 -53
- package/src/__snapshots__/useQuery.test.tsx.snap +2011 -0
- package/src/__snapshots__/useRow.test.tsx.snap +347 -151
- package/src/__tests__/fixture.tsx +11 -19
- package/src/experimental/components/LiveList.tsx +8 -7
- package/src/mod.ts +0 -1
- package/src/useAtom.ts +22 -11
- package/src/useQuery.test.tsx +165 -67
- package/src/useQuery.ts +84 -54
- package/src/useRcResource.test.tsx +167 -0
- package/src/useRcResource.ts +180 -0
- package/src/useRow.test.tsx +130 -164
- package/src/useRow.ts +32 -35
- package/src/utils/useStateRefWithReactiveInput.ts +1 -1
- package/dist/useTemporaryQuery.d.ts +0 -22
- package/dist/useTemporaryQuery.d.ts.map +0 -1
- package/dist/useTemporaryQuery.js +0 -75
- package/dist/useTemporaryQuery.js.map +0 -1
- package/src/useScopedQuery.test.tsx +0 -96
- package/src/useScopedQuery.ts +0 -142
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import * as ReactTesting from '@testing-library/react';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
import { __resetUseRcResourceCache, useRcResource } from './useRcResource.js';
|
|
5
|
+
describe.each([{ strictMode: true }, { strictMode: false }])('useRcResource (strictMode=%s)', ({ strictMode }) => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
__resetUseRcResourceCache();
|
|
8
|
+
});
|
|
9
|
+
const wrapper = strictMode ? React.StrictMode : React.Fragment;
|
|
10
|
+
it('should create a stateful entity using make and call cleanup on unmount', () => {
|
|
11
|
+
const makeSpy = vi.fn(() => Symbol('statefulResource'));
|
|
12
|
+
const cleanupSpy = vi.fn();
|
|
13
|
+
const { result, unmount } = ReactTesting.renderHook(() => useRcResource('key-1', makeSpy, cleanupSpy), { wrapper });
|
|
14
|
+
expect(makeSpy).toHaveBeenCalledTimes(1);
|
|
15
|
+
expect(result.current).toBeDefined();
|
|
16
|
+
expect(cleanupSpy).toHaveBeenCalledTimes(0);
|
|
17
|
+
unmount();
|
|
18
|
+
expect(cleanupSpy).toHaveBeenCalledTimes(1);
|
|
19
|
+
});
|
|
20
|
+
it('should reuse the same entity when the key remains unchanged', () => {
|
|
21
|
+
const makeSpy = vi.fn(() => Symbol('statefulResource'));
|
|
22
|
+
const cleanupSpy = vi.fn();
|
|
23
|
+
const { result, rerender, unmount } = ReactTesting.renderHook(({ key }) => useRcResource(key, makeSpy, cleanupSpy), { initialProps: { key: 'consistent-key' }, wrapper });
|
|
24
|
+
const instance1 = result.current;
|
|
25
|
+
// Re-render with the same key
|
|
26
|
+
rerender({ key: 'consistent-key' });
|
|
27
|
+
const instance2 = result.current;
|
|
28
|
+
expect(instance1).toBe(instance2);
|
|
29
|
+
expect(makeSpy).toHaveBeenCalledTimes(1);
|
|
30
|
+
unmount();
|
|
31
|
+
expect(cleanupSpy).toHaveBeenCalledTimes(1);
|
|
32
|
+
});
|
|
33
|
+
it('should dispose the previous instance when the key changes', () => {
|
|
34
|
+
const makeSpy = vi.fn(() => Symbol('statefulResource'));
|
|
35
|
+
const cleanupSpy = vi.fn();
|
|
36
|
+
const { result, rerender, unmount } = ReactTesting.renderHook(({ key }) => useRcResource(key, makeSpy, cleanupSpy), { initialProps: { key: 'a' }, wrapper });
|
|
37
|
+
const instanceA = result.current;
|
|
38
|
+
// Change the key; this should trigger the disposal of the 'a' instance
|
|
39
|
+
rerender({ key: 'b' });
|
|
40
|
+
const instanceB = result.current;
|
|
41
|
+
expect(instanceA).not.toBe(instanceB);
|
|
42
|
+
expect(makeSpy).toHaveBeenCalledTimes(2);
|
|
43
|
+
expect(cleanupSpy).toHaveBeenCalledTimes(1);
|
|
44
|
+
unmount();
|
|
45
|
+
expect(cleanupSpy).toHaveBeenCalledTimes(2);
|
|
46
|
+
});
|
|
47
|
+
it('should not dispose the entity until all consumers unmount', () => {
|
|
48
|
+
const makeSpy = vi.fn(() => Symbol('statefulResource'));
|
|
49
|
+
const cleanupSpy = vi.fn();
|
|
50
|
+
// Simulate two consumers using the same key independently.
|
|
51
|
+
const { unmount: unmount1 } = ReactTesting.renderHook(() => useRcResource('shared-key', makeSpy, cleanupSpy), {
|
|
52
|
+
wrapper,
|
|
53
|
+
});
|
|
54
|
+
const { unmount: unmount2, result } = ReactTesting.renderHook(() => useRcResource('shared-key', makeSpy, cleanupSpy), {
|
|
55
|
+
wrapper,
|
|
56
|
+
});
|
|
57
|
+
expect(result.current).toBeDefined();
|
|
58
|
+
expect(makeSpy).toHaveBeenCalledTimes(1);
|
|
59
|
+
// Unmount first consumer; the entity should remain active.
|
|
60
|
+
unmount1();
|
|
61
|
+
expect(cleanupSpy).not.toHaveBeenCalled();
|
|
62
|
+
// Unmount second consumer; now the entity is disposed.
|
|
63
|
+
unmount2();
|
|
64
|
+
expect(cleanupSpy).toHaveBeenCalledTimes(1);
|
|
65
|
+
});
|
|
66
|
+
it('should handle rapid key changes correctly', () => {
|
|
67
|
+
const makeSpy = vi.fn(() => Symbol('statefulResource'));
|
|
68
|
+
const cleanupSpy = vi.fn();
|
|
69
|
+
const { rerender, unmount } = ReactTesting.renderHook(({ key }) => useRcResource(key, makeSpy, cleanupSpy), {
|
|
70
|
+
initialProps: { key: '1' },
|
|
71
|
+
wrapper,
|
|
72
|
+
});
|
|
73
|
+
// Rapid sequence of key changes.
|
|
74
|
+
rerender({ key: '2' });
|
|
75
|
+
rerender({ key: '3' });
|
|
76
|
+
// Expect three creations: one each for keys '1', '2', '3'
|
|
77
|
+
expect(makeSpy).toHaveBeenCalledTimes(3);
|
|
78
|
+
// Cleanup should have been triggered for key '1' and key '2'
|
|
79
|
+
expect(cleanupSpy).toHaveBeenCalledTimes(2);
|
|
80
|
+
unmount();
|
|
81
|
+
// Unmounting the final consumer disposes the key '3' instance.
|
|
82
|
+
expect(cleanupSpy).toHaveBeenCalledTimes(3);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
// This code was useful to better understand the hook behaviour with and without strict mode
|
|
86
|
+
// describe('debug', () => {
|
|
87
|
+
// const useStrictTest = (key: string) => {
|
|
88
|
+
// const id = React.useId()
|
|
89
|
+
// console.log(key, 'id', id)
|
|
90
|
+
// const x = React.useMemo(() => {
|
|
91
|
+
// console.log('useMemo', key)
|
|
92
|
+
// return 'hi' + key
|
|
93
|
+
// }, [key])
|
|
94
|
+
// React.useEffect(() => {
|
|
95
|
+
// console.log('useEffect', key)
|
|
96
|
+
// return () => {
|
|
97
|
+
// console.log('unmount', key)
|
|
98
|
+
// }
|
|
99
|
+
// }, [])
|
|
100
|
+
// return x
|
|
101
|
+
// }
|
|
102
|
+
// it('strict mode component', () => {
|
|
103
|
+
// console.log('strict mode component')
|
|
104
|
+
// const Root = () => {
|
|
105
|
+
// useStrictTest('a')
|
|
106
|
+
// return null
|
|
107
|
+
// }
|
|
108
|
+
// const { unmount } = ReactTesting.render(
|
|
109
|
+
// <React.StrictMode>
|
|
110
|
+
// <Root />
|
|
111
|
+
// </React.StrictMode>,
|
|
112
|
+
// )
|
|
113
|
+
// unmount()
|
|
114
|
+
// })
|
|
115
|
+
// it('strict mode hook', () => {
|
|
116
|
+
// console.log('strict mode hook')
|
|
117
|
+
// const wrapper: React.FC<{ children: React.ReactNode }> = React.StrictMode
|
|
118
|
+
// const { unmount } = ReactTesting.renderHook(() => useStrictTest('b'), { wrapper })
|
|
119
|
+
// unmount()
|
|
120
|
+
// })
|
|
121
|
+
// })
|
|
122
|
+
//# sourceMappingURL=useRcResource.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useRcResource.test.js","sourceRoot":"","sources":["../src/useRcResource.test.tsx"],"names":[],"mappings":"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"}
|
package/dist/useRow.d.ts
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import type { QueryInfo, RowQuery } from '@livestore/common';
|
|
2
2
|
import { SessionIdSymbol } from '@livestore/common';
|
|
3
3
|
import { DbSchema } from '@livestore/common/schema';
|
|
4
|
-
import type { LiveQuery,
|
|
4
|
+
import type { LiveQuery, Store } from '@livestore/livestore';
|
|
5
5
|
export type UseRowResult<TTableDef extends DbSchema.TableDefBase> = [
|
|
6
6
|
row: RowQuery.Result<TTableDef>,
|
|
7
7
|
setRow: StateSetters<TTableDef>,
|
|
8
8
|
query$: LiveQuery<RowQuery.Result<TTableDef>, QueryInfo>
|
|
9
9
|
];
|
|
10
|
-
export type UseRowOptionsBase = {
|
|
11
|
-
reactivityGraph?: ReactivityGraph;
|
|
12
|
-
};
|
|
13
10
|
/**
|
|
14
11
|
* Similar to `React.useState` but returns a tuple of `[row, setRow, query$]` for a given table where ...
|
|
15
12
|
*
|
|
@@ -25,20 +22,26 @@ export declare const useRow: {
|
|
|
25
22
|
deriveMutations: {
|
|
26
23
|
enabled: true;
|
|
27
24
|
};
|
|
28
|
-
}>>(table: TTableDef, options?:
|
|
25
|
+
}>>(table: TTableDef, options?: {
|
|
26
|
+
store?: Store;
|
|
27
|
+
}): UseRowResult<TTableDef>;
|
|
29
28
|
<TTableDef extends DbSchema.TableDef<DbSchema.DefaultSqliteTableDef, DbSchema.TableOptions & {
|
|
30
29
|
isSingleton: false;
|
|
31
30
|
requiredInsertColumnNames: 'id';
|
|
32
31
|
deriveMutations: {
|
|
33
32
|
enabled: true;
|
|
34
33
|
};
|
|
35
|
-
}>>(table: TTableDef, id: string | SessionIdSymbol, options?:
|
|
34
|
+
}>>(table: TTableDef, id: string | SessionIdSymbol, options?: Partial<RowQuery.RequiredColumnsOptions<TTableDef>> & {
|
|
35
|
+
store?: Store;
|
|
36
|
+
}): UseRowResult<TTableDef>;
|
|
36
37
|
<TTableDef extends DbSchema.TableDef<DbSchema.DefaultSqliteTableDef, DbSchema.TableOptions & {
|
|
37
38
|
isSingleton: false;
|
|
38
39
|
deriveMutations: {
|
|
39
40
|
enabled: true;
|
|
40
41
|
};
|
|
41
|
-
}>>(table: TTableDef, id: string | SessionIdSymbol, options:
|
|
42
|
+
}>>(table: TTableDef, id: string | SessionIdSymbol, options: RowQuery.RequiredColumnsOptions<TTableDef> & {
|
|
43
|
+
store?: Store;
|
|
44
|
+
}): UseRowResult<TTableDef>;
|
|
42
45
|
};
|
|
43
46
|
export type Dispatch<A> = (action: A) => void;
|
|
44
47
|
export type SetStateAction<S> = S | ((previousValue: S) => S);
|
package/dist/useRow.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useRow.d.ts","sourceRoot":"","sources":["../src/useRow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AAEnD,OAAO,KAAK,EAAE,SAAS,
|
|
1
|
+
{"version":3,"file":"useRow.d.ts","sourceRoot":"","sources":["../src/useRow.ts"],"names":[],"mappings":"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"}
|
package/dist/useRow.js
CHANGED
|
@@ -6,7 +6,6 @@ import { ReadonlyRecord } from '@livestore/utils/effect';
|
|
|
6
6
|
import React from 'react';
|
|
7
7
|
import { useStore } from './LiveStoreContext.js';
|
|
8
8
|
import { useQueryRef } from './useQuery.js';
|
|
9
|
-
import { useMakeScopedQuery } from './useScopedQuery.js';
|
|
10
9
|
/**
|
|
11
10
|
* Similar to `React.useState` but returns a tuple of `[row, setRow, query$]` for a given table where ...
|
|
12
11
|
*
|
|
@@ -20,12 +19,12 @@ export const useRow = (table, idOrOptions, options_) => {
|
|
|
20
19
|
const sqliteTableDef = table.sqliteDef;
|
|
21
20
|
const id = typeof idOrOptions === 'string' || idOrOptions === SessionIdSymbol ? idOrOptions : undefined;
|
|
22
21
|
const options = typeof idOrOptions === 'string' || idOrOptions === SessionIdSymbol ? options_ : idOrOptions;
|
|
23
|
-
const { insertValues
|
|
22
|
+
const { insertValues } = options ?? {};
|
|
24
23
|
const tableName = table.sqliteDef.name;
|
|
25
24
|
if (DbSchema.tableHasDerivedMutations(table) === false) {
|
|
26
25
|
shouldNeverHappen(`useRow called on table "${tableName}" which does not have 'deriveMutations: true' set`);
|
|
27
26
|
}
|
|
28
|
-
const { store } = useStore();
|
|
27
|
+
const { store } = useStore({ store: options?.store });
|
|
29
28
|
if (store.schema.tables.has(table.sqliteDef.name) === false &&
|
|
30
29
|
table.sqliteDef.name.startsWith('__livestore') === false) {
|
|
31
30
|
shouldNeverHappen(`Table "${table.sqliteDef.name}" not found in schema`);
|
|
@@ -33,20 +32,18 @@ export const useRow = (table, idOrOptions, options_) => {
|
|
|
33
32
|
// console.debug('useRow', tableName, id)
|
|
34
33
|
const idStr = id === SessionIdSymbol ? 'session' : id;
|
|
35
34
|
const rowQuery = table.query.row;
|
|
36
|
-
const
|
|
37
|
-
? queryDb(rowQuery(), {
|
|
38
|
-
: queryDb(rowQuery(id, { insertValues: insertValues }), {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
},
|
|
35
|
+
const queryDef = React.useMemo(() => DbSchema.tableIsSingleton(table)
|
|
36
|
+
? queryDb(rowQuery(), {})
|
|
37
|
+
: queryDb(rowQuery(id, { insertValues: insertValues }), { deps: idStr }), [id, insertValues, rowQuery, table, idStr]);
|
|
38
|
+
const queryRef = useQueryRef(queryDef, {
|
|
39
|
+
otelSpanName: `LiveStore:useRow:${tableName}${idStr === undefined ? '' : `:${idStr}`}`,
|
|
40
|
+
store: options?.store,
|
|
43
41
|
});
|
|
44
|
-
const query$Ref = useQueryRef(query$, otelContext);
|
|
45
42
|
const setState = React.useMemo(() => {
|
|
46
43
|
if (table.options.isSingleColumn) {
|
|
47
44
|
return (newValueOrFn) => {
|
|
48
|
-
const newValue = typeof newValueOrFn === 'function' ? newValueOrFn(
|
|
49
|
-
if (
|
|
45
|
+
const newValue = typeof newValueOrFn === 'function' ? newValueOrFn(queryRef.valueRef.current) : newValueOrFn;
|
|
46
|
+
if (queryRef.valueRef.current === newValue)
|
|
50
47
|
return;
|
|
51
48
|
// NOTE we need to account for the short-hand syntax for single-column+singleton tables
|
|
52
49
|
if (table.options.isSingleton) {
|
|
@@ -63,10 +60,10 @@ export const useRow = (table, idOrOptions, options_) => {
|
|
|
63
60
|
ReadonlyRecord.map(sqliteTableDef.columns, (column, columnName) => (newValueOrFn) => {
|
|
64
61
|
const newValue =
|
|
65
62
|
// @ts-expect-error TODO fix typing
|
|
66
|
-
typeof newValueOrFn === 'function' ? newValueOrFn(
|
|
63
|
+
typeof newValueOrFn === 'function' ? newValueOrFn(queryRef.valueRef.current[columnName]) : newValueOrFn;
|
|
67
64
|
// Don't update the state if it's the same as the value already seen in the component
|
|
68
65
|
// @ts-expect-error TODO fix typing
|
|
69
|
-
if (
|
|
66
|
+
if (queryRef.valueRef.current[columnName] === newValue)
|
|
70
67
|
return;
|
|
71
68
|
store.mutate(table.update({ where: { id: id ?? 'singleton' }, values: { [columnName]: newValue } }));
|
|
72
69
|
// store.mutate(updateMutationForQueryInfo(query$.queryInfo!, { [columnName]: newValue }))
|
|
@@ -74,12 +71,12 @@ export const useRow = (table, idOrOptions, options_) => {
|
|
|
74
71
|
setState.setMany = (columnValuesOrFn) => {
|
|
75
72
|
const columnValues =
|
|
76
73
|
// @ts-expect-error TODO fix typing
|
|
77
|
-
typeof columnValuesOrFn === 'function' ? columnValuesOrFn(
|
|
74
|
+
typeof columnValuesOrFn === 'function' ? columnValuesOrFn(queryRef.valueRef.current) : columnValuesOrFn;
|
|
78
75
|
// TODO use hashing instead
|
|
79
76
|
// Don't update the state if it's the same as the value already seen in the component
|
|
80
77
|
if (
|
|
81
78
|
// @ts-expect-error TODO fix typing
|
|
82
|
-
Object.entries(columnValues).every(([columnName, value]) =>
|
|
79
|
+
Object.entries(columnValues).every(([columnName, value]) => queryRef.valueRef.current[columnName] === value)) {
|
|
83
80
|
return;
|
|
84
81
|
}
|
|
85
82
|
store.mutate(table.update({ where: { id: id ?? 'singleton' }, values: columnValues }));
|
|
@@ -87,7 +84,7 @@ export const useRow = (table, idOrOptions, options_) => {
|
|
|
87
84
|
};
|
|
88
85
|
return setState;
|
|
89
86
|
}
|
|
90
|
-
}, [id,
|
|
91
|
-
return [
|
|
87
|
+
}, [id, queryRef.valueRef, sqliteTableDef.columns, store, table]);
|
|
88
|
+
return [queryRef.valueRef.current, setState, queryRef.queryRcRef.value];
|
|
92
89
|
};
|
|
93
90
|
//# sourceMappingURL=useRow.js.map
|
package/dist/useRow.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useRow.js","sourceRoot":"","sources":["../src/useRow.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AAGnD,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AACxD,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"useRow.js","sourceRoot":"","sources":["../src/useRow.ts"],"names":[],"mappings":"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"}
|
package/dist/useRow.test.js
CHANGED
|
@@ -1,85 +1,77 @@
|
|
|
1
1
|
import * as LiveStore from '@livestore/livestore';
|
|
2
2
|
import { getSimplifiedRootSpan } from '@livestore/livestore/internal/testing-utils';
|
|
3
3
|
import { Effect, ReadonlyRecord, Schema } from '@livestore/utils/effect';
|
|
4
|
+
import { Vitest } from '@livestore/utils/node-vitest';
|
|
4
5
|
import * as otel from '@opentelemetry/api';
|
|
5
6
|
import { BasicTracerProvider, InMemorySpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base';
|
|
6
|
-
import
|
|
7
|
+
import * as ReactTesting from '@testing-library/react';
|
|
7
8
|
import React from 'react';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
9
|
+
import { beforeEach, expect, it } from 'vitest';
|
|
10
|
+
import { AppRouterSchema, makeTodoMvcReact, tables, todos } from './__tests__/fixture.js';
|
|
10
11
|
import * as LiveStoreReact from './mod.js';
|
|
12
|
+
import { __resetUseRcResourceCache } from './useRcResource.js';
|
|
13
|
+
// const strictMode = process.env.REACT_STRICT_MODE !== undefined
|
|
11
14
|
// NOTE running tests concurrently doesn't work with the default global db graph
|
|
12
|
-
describe('useRow', () => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const renderCount =
|
|
18
|
-
const { result, rerender } = renderHook((userId) => {
|
|
15
|
+
Vitest.describe('useRow', () => {
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
__resetUseRcResourceCache();
|
|
18
|
+
});
|
|
19
|
+
Vitest.scopedLive('should update the data based on component key', () => Effect.gen(function* () {
|
|
20
|
+
const { wrapper, store, renderCount } = yield* makeTodoMvcReact({});
|
|
21
|
+
const { result, rerender } = ReactTesting.renderHook((userId) => {
|
|
19
22
|
renderCount.inc();
|
|
20
|
-
const [state, setState] = LiveStoreReact.useRow(
|
|
23
|
+
const [state, setState] = LiveStoreReact.useRow(tables.userInfo, userId);
|
|
21
24
|
return { state, setState };
|
|
22
25
|
}, { wrapper, initialProps: 'u1' });
|
|
23
26
|
expect(result.current.state.id).toBe('u1');
|
|
24
27
|
expect(result.current.state.username).toBe('');
|
|
25
28
|
expect(renderCount.val).toBe(1);
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
})));
|
|
29
|
+
expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot();
|
|
30
|
+
store.mutate(tables.userInfo.insert({ id: 'u2', username: 'username_u2' }));
|
|
29
31
|
rerender('u2');
|
|
32
|
+
expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot();
|
|
30
33
|
expect(result.current.state.id).toBe('u2');
|
|
31
34
|
expect(result.current.state.username).toBe('username_u2');
|
|
32
35
|
expect(renderCount.val).toBe(2);
|
|
33
|
-
})
|
|
36
|
+
}));
|
|
34
37
|
// TODO add a test that makes sure React doesn't re-render when a setter is used to set the same value
|
|
35
|
-
|
|
36
|
-
const { wrapper,
|
|
37
|
-
|
|
38
|
-
});
|
|
39
|
-
const renderCount = makeRenderCount();
|
|
40
|
-
const { result } = renderHook((userId) => {
|
|
38
|
+
Vitest.scopedLive('should update the data reactively - via setState', () => Effect.gen(function* () {
|
|
39
|
+
const { wrapper, renderCount } = yield* makeTodoMvcReact({});
|
|
40
|
+
const { result } = ReactTesting.renderHook((userId) => {
|
|
41
41
|
renderCount.inc();
|
|
42
|
-
const [state, setState] = LiveStoreReact.useRow(
|
|
42
|
+
const [state, setState] = LiveStoreReact.useRow(tables.userInfo, userId);
|
|
43
43
|
return { state, setState };
|
|
44
44
|
}, { wrapper, initialProps: 'u1' });
|
|
45
45
|
expect(result.current.state.id).toBe('u1');
|
|
46
46
|
expect(result.current.state.username).toBe('');
|
|
47
47
|
expect(renderCount.val).toBe(1);
|
|
48
|
-
|
|
48
|
+
ReactTesting.act(() => result.current.setState.username('username_u1_hello'));
|
|
49
49
|
expect(result.current.state.id).toBe('u1');
|
|
50
50
|
expect(result.current.state.username).toBe('username_u1_hello');
|
|
51
51
|
expect(renderCount.val).toBe(2);
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
const { wrapper, store,
|
|
55
|
-
|
|
56
|
-
});
|
|
57
|
-
const renderCount = makeRenderCount();
|
|
58
|
-
const { result } = renderHook((userId) => {
|
|
52
|
+
}));
|
|
53
|
+
Vitest.scopedLive('should update the data reactively - via raw store mutation', () => Effect.gen(function* () {
|
|
54
|
+
const { wrapper, store, renderCount } = yield* makeTodoMvcReact({});
|
|
55
|
+
const { result } = ReactTesting.renderHook((userId) => {
|
|
59
56
|
renderCount.inc();
|
|
60
|
-
const [state, setState] = LiveStoreReact.useRow(
|
|
57
|
+
const [state, setState] = LiveStoreReact.useRow(tables.userInfo, userId);
|
|
61
58
|
return { state, setState };
|
|
62
59
|
}, { wrapper, initialProps: 'u1' });
|
|
63
60
|
expect(result.current.state.id).toBe('u1');
|
|
64
61
|
expect(result.current.state.username).toBe('');
|
|
65
62
|
expect(renderCount.val).toBe(1);
|
|
66
|
-
|
|
67
|
-
sql: LiveStore.sql `UPDATE UserInfo SET username = 'username_u1_hello' WHERE id = 'u1';`,
|
|
68
|
-
})));
|
|
63
|
+
ReactTesting.act(() => store.mutate(tables.userInfo.update({ where: { id: 'u1' }, values: { username: 'username_u1_hello' } })));
|
|
69
64
|
expect(result.current.state.id).toBe('u1');
|
|
70
65
|
expect(result.current.state.username).toBe('username_u1_hello');
|
|
71
66
|
expect(renderCount.val).toBe(2);
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
const { wrapper, store,
|
|
75
|
-
|
|
76
|
-
});
|
|
77
|
-
const allTodos$ = LiveStore.queryDb({ query: `select * from todos`, schema: Schema.Array(tables.todos.schema) }, { label: 'allTodos', reactivityGraph });
|
|
78
|
-
const appRouterRenderCount = makeRenderCount();
|
|
67
|
+
}));
|
|
68
|
+
Vitest.scopedLive('should work for a larger app', () => Effect.gen(function* () {
|
|
69
|
+
const { wrapper, store, renderCount } = yield* makeTodoMvcReact({});
|
|
70
|
+
const allTodos$ = LiveStore.queryDb({ query: `select * from todos`, schema: Schema.Array(tables.todos.schema) }, { label: 'allTodos' });
|
|
79
71
|
let globalSetState;
|
|
80
72
|
const AppRouter = () => {
|
|
81
|
-
|
|
82
|
-
const [state, setState] = LiveStoreReact.useRow(AppRouterSchema
|
|
73
|
+
renderCount.inc();
|
|
74
|
+
const [state, setState] = LiveStoreReact.useRow(AppRouterSchema);
|
|
83
75
|
globalSetState = setState;
|
|
84
76
|
return (React.createElement("div", null,
|
|
85
77
|
React.createElement(TasksList, { setTaskId: setState.currentTaskId }),
|
|
@@ -93,94 +85,85 @@ describe('useRow', () => {
|
|
|
93
85
|
return (React.createElement("div", null, allTodos.map((_) => (React.createElement("div", { key: _.id, onClick: () => setTaskId(_.id) }, _.id)))));
|
|
94
86
|
};
|
|
95
87
|
const TaskDetails = ({ id }) => {
|
|
96
|
-
const [todo] = LiveStoreReact.useRow(todos, id
|
|
88
|
+
const [todo] = LiveStoreReact.useRow(todos, id);
|
|
97
89
|
return React.createElement("div", { role: "content" }, JSON.stringify(todo));
|
|
98
90
|
};
|
|
99
|
-
const renderResult = render(React.createElement(AppRouter, null), { wrapper });
|
|
100
|
-
expect(
|
|
101
|
-
|
|
91
|
+
const renderResult = ReactTesting.render(React.createElement(AppRouter, null), { wrapper });
|
|
92
|
+
expect(renderCount.val).toBe(1);
|
|
93
|
+
ReactTesting.act(() => store.mutate(LiveStore.rawSqlMutation({
|
|
102
94
|
sql: LiveStore.sql `INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)`,
|
|
103
95
|
})));
|
|
104
|
-
expect(
|
|
96
|
+
expect(renderCount.val).toBe(1);
|
|
105
97
|
expect(renderResult.getByRole('current-id').innerHTML).toMatchInlineSnapshot('"Current Task Id: -"');
|
|
106
|
-
|
|
107
|
-
expect(
|
|
98
|
+
ReactTesting.act(() => globalSetState.currentTaskId('t1'));
|
|
99
|
+
expect(renderCount.val).toBe(2);
|
|
108
100
|
expect(renderResult.getByRole('content').innerHTML).toMatchInlineSnapshot(`"{"id":"t1","text":"buy milk","completed":false}"`);
|
|
109
101
|
expect(renderResult.getByRole('current-id').innerHTML).toMatchInlineSnapshot('"Current Task Id: t1"');
|
|
110
|
-
|
|
102
|
+
ReactTesting.act(() => store.mutate(LiveStore.rawSqlMutation({
|
|
111
103
|
sql: LiveStore.sql `INSERT INTO todos (id, text, completed) VALUES ('t2', 'buy eggs', 0)`,
|
|
112
104
|
}), AppRouterSchema.update({ where: { id: 'singleton' }, values: { currentTaskId: 't2' } }), LiveStore.rawSqlMutation({
|
|
113
105
|
sql: LiveStore.sql `INSERT INTO todos (id, text, completed) VALUES ('t3', 'buy bread', 0)`,
|
|
114
106
|
})));
|
|
115
|
-
expect(
|
|
107
|
+
expect(renderCount.val).toBe(3);
|
|
116
108
|
expect(renderResult.getByRole('current-id').innerHTML).toMatchInlineSnapshot('"Current Task Id: t2"');
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
const { store, wrapper,
|
|
120
|
-
useGlobalReactivityGraph: false,
|
|
121
|
-
});
|
|
122
|
-
const renderCount = makeRenderCount();
|
|
109
|
+
}));
|
|
110
|
+
Vitest.scopedLive('should work for a useRow query chained with a useTemporary query', () => Effect.gen(function* () {
|
|
111
|
+
const { store, wrapper, renderCount } = yield* makeTodoMvcReact({});
|
|
123
112
|
store.mutate(todos.insert({ id: 't1', text: 'buy milk', completed: false }), todos.insert({ id: 't2', text: 'buy bread', completed: false }));
|
|
124
|
-
const { result, unmount, rerender } = renderHook((userId) => {
|
|
113
|
+
const { result, unmount, rerender } = ReactTesting.renderHook((userId) => {
|
|
125
114
|
renderCount.inc();
|
|
126
|
-
const [_row, _setRow, rowState$] = LiveStoreReact.useRow(
|
|
127
|
-
const todos = LiveStoreReact.
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
115
|
+
const [_row, _setRow, rowState$] = LiveStoreReact.useRow(tables.userInfo, userId);
|
|
116
|
+
const todos = LiveStoreReact.useQuery(LiveStore.queryDb((get) => tables.todos.query.where('text', 'LIKE', `%${get(rowState$).text}%`),
|
|
117
|
+
// TODO find a way where explicit `userId` is not needed here
|
|
118
|
+
// possibly by automatically understanding the `get(rowState$)` dependency
|
|
119
|
+
{ label: 'todosFiltered', deps: userId }));
|
|
131
120
|
return { todos };
|
|
132
121
|
}, { wrapper, initialProps: 'u1' });
|
|
133
|
-
|
|
134
|
-
sql: LiveStore.sql `INSERT INTO UserInfo (id, username, text) VALUES ('u2', 'username_u2', 'milk')`,
|
|
135
|
-
})));
|
|
122
|
+
ReactTesting.act(() => store.mutate(tables.userInfo.insert({ id: 'u2', username: 'username_u2', text: 'milk' })));
|
|
136
123
|
expect(result.current.todos.length).toBe(2);
|
|
137
|
-
// expect(result.current.state.username).toBe('')
|
|
138
124
|
expect(renderCount.val).toBe(1);
|
|
139
125
|
rerender('u2');
|
|
140
126
|
expect(result.current.todos.length).toBe(1);
|
|
141
127
|
expect(renderCount.val).toBe(2);
|
|
142
128
|
unmount();
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const exporter = new InMemorySpanExporter();
|
|
147
|
-
const provider = cachedProvider ?? new BasicTracerProvider();
|
|
148
|
-
cachedProvider = provider;
|
|
149
|
-
provider.addSpanProcessor(new SimpleSpanProcessor(exporter));
|
|
129
|
+
}));
|
|
130
|
+
Vitest.describe('otel', () => {
|
|
131
|
+
const provider = new BasicTracerProvider({});
|
|
150
132
|
provider.register();
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const {
|
|
156
|
-
|
|
157
|
-
|
|
133
|
+
it.each([{ strictMode: true }, { strictMode: false }])('should update the data based on component key strictMode=%s', async ({ strictMode }) => {
|
|
134
|
+
const exporter = new InMemorySpanExporter();
|
|
135
|
+
// const provider = cachedProvider ?? new BasicTracerProvider({ spanProcessors: [new SimpleSpanProcessor(exporter)] })
|
|
136
|
+
provider.addSpanProcessor(new SimpleSpanProcessor(exporter));
|
|
137
|
+
const otelTracer = otel.trace.getTracer(`testing-${strictMode ? 'strict' : 'non-strict'}`);
|
|
138
|
+
const span = otelTracer.startSpan('test-root');
|
|
139
|
+
const otelContext = otel.trace.setSpan(otel.context.active(), span);
|
|
140
|
+
await Effect.gen(function* () {
|
|
141
|
+
const { wrapper, store, renderCount } = yield* makeTodoMvcReact({
|
|
158
142
|
otelContext,
|
|
159
143
|
otelTracer,
|
|
144
|
+
strictMode,
|
|
160
145
|
});
|
|
161
|
-
const
|
|
162
|
-
const { result, rerender, unmount } = renderHook((userId) => {
|
|
146
|
+
const { result, rerender, unmount } = ReactTesting.renderHook((userId) => {
|
|
163
147
|
renderCount.inc();
|
|
164
|
-
const [state, setState] = LiveStoreReact.useRow(
|
|
148
|
+
const [state, setState] = LiveStoreReact.useRow(tables.userInfo, userId);
|
|
165
149
|
return { state, setState };
|
|
166
150
|
}, { wrapper, initialProps: 'u1' });
|
|
167
151
|
expect(result.current.state.id).toBe('u1');
|
|
168
152
|
expect(result.current.state.username).toBe('');
|
|
169
153
|
expect(renderCount.val).toBe(1);
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
})));
|
|
154
|
+
// For u2 we'll make sure that the row already exists,
|
|
155
|
+
// so the lazy `insert` will be skipped
|
|
156
|
+
ReactTesting.act(() => store.mutate(tables.userInfo.insert({ id: 'u2', username: 'username_u2' })));
|
|
173
157
|
rerender('u2');
|
|
174
158
|
expect(result.current.state.id).toBe('u2');
|
|
175
159
|
expect(result.current.state.username).toBe('username_u2');
|
|
176
160
|
expect(renderCount.val).toBe(2);
|
|
177
161
|
unmount();
|
|
178
162
|
span.end();
|
|
179
|
-
return { strictMode };
|
|
180
163
|
}).pipe(Effect.scoped, Effect.tapCauseLogPretty, Effect.runPromise);
|
|
181
164
|
const mapAttributes = (attributes) => {
|
|
182
165
|
return ReadonlyRecord.map(attributes, (val, key) => {
|
|
183
|
-
if (key === '
|
|
166
|
+
if (key === 'firstStackInfo') {
|
|
184
167
|
const stackInfo = JSON.parse(val);
|
|
185
168
|
// stackInfo.frames.shift() // Removes `renderHook.wrapper` from the stack
|
|
186
169
|
stackInfo.frames.forEach((_) => {
|
|
@@ -194,13 +177,7 @@ describe('useRow', () => {
|
|
|
194
177
|
return val;
|
|
195
178
|
});
|
|
196
179
|
};
|
|
197
|
-
|
|
198
|
-
if (strictMode) {
|
|
199
|
-
expect(getSimplifiedRootSpan(exporter, mapAttributes)).toMatchSnapshot('strictMode=true');
|
|
200
|
-
}
|
|
201
|
-
else {
|
|
202
|
-
expect(getSimplifiedRootSpan(exporter, mapAttributes)).toMatchSnapshot('strictMode=false');
|
|
203
|
-
}
|
|
180
|
+
expect(getSimplifiedRootSpan(exporter, mapAttributes)).toMatchSnapshot();
|
|
204
181
|
});
|
|
205
182
|
});
|
|
206
183
|
});
|