@livestore/react 0.3.0-dev.9 → 0.3.1-dev.0
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/LICENSE +201 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/LiveStoreContext.d.ts +10 -4
- package/dist/LiveStoreContext.d.ts.map +1 -1
- package/dist/LiveStoreContext.js +1 -11
- package/dist/LiveStoreContext.js.map +1 -1
- package/dist/LiveStoreProvider.d.ts +29 -12
- package/dist/LiveStoreProvider.d.ts.map +1 -1
- package/dist/LiveStoreProvider.js +84 -55
- package/dist/LiveStoreProvider.js.map +1 -1
- package/dist/LiveStoreProvider.test.js +80 -29
- package/dist/LiveStoreProvider.test.js.map +1 -1
- package/dist/__tests__/fixture.d.ts +122 -556
- package/dist/__tests__/fixture.d.ts.map +1 -1
- package/dist/__tests__/fixture.js +71 -30
- 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 +10 -6
- package/dist/experimental/components/LiveList.js.map +1 -1
- package/dist/mod.d.ts +4 -5
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +4 -5
- package/dist/mod.js.map +1 -1
- package/dist/useClientDocument.d.ts +61 -0
- package/dist/useClientDocument.d.ts.map +1 -0
- package/dist/useClientDocument.js +79 -0
- package/dist/useClientDocument.js.map +1 -0
- package/dist/useClientDocument.test.d.ts +2 -0
- package/dist/useClientDocument.test.d.ts.map +1 -0
- package/dist/useClientDocument.test.js +175 -0
- package/dist/useClientDocument.test.js.map +1 -0
- package/dist/useQuery.d.ts +25 -3
- package/dist/useQuery.d.ts.map +1 -1
- package/dist/useQuery.js +67 -47
- package/dist/useQuery.js.map +1 -1
- package/dist/useQuery.test.d.ts +1 -1
- package/dist/useQuery.test.d.ts.map +1 -1
- package/dist/useQuery.test.js +86 -24
- package/dist/useQuery.test.js.map +1 -1
- package/dist/useRcResource.d.ts +76 -0
- package/dist/useRcResource.d.ts.map +1 -0
- package/dist/useRcResource.js +152 -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/useStore.d.ts +9 -0
- package/dist/useStore.d.ts.map +1 -0
- package/dist/useStore.js +28 -0
- package/dist/useStore.js.map +1 -0
- package/dist/utils/useStateRefWithReactiveInput.d.ts.map +1 -1
- package/package.json +20 -13
- package/src/LiveStoreContext.ts +11 -16
- package/src/LiveStoreProvider.test.tsx +176 -37
- package/src/LiveStoreProvider.tsx +156 -81
- package/src/__snapshots__/useClientDocument.test.tsx.snap +613 -0
- package/src/__snapshots__/useQuery.test.tsx.snap +2011 -0
- package/src/__tests__/fixture.tsx +74 -47
- package/src/experimental/components/LiveList.tsx +10 -7
- package/src/mod.ts +5 -6
- package/src/useClientDocument.test.tsx +306 -0
- package/src/useClientDocument.ts +157 -0
- package/src/useQuery.test.tsx +182 -71
- package/src/useQuery.ts +95 -58
- package/src/useRcResource.test.tsx +167 -0
- package/src/useRcResource.ts +182 -0
- package/src/useStore.ts +36 -0
- package/dist/useAtom.d.ts +0 -5
- package/dist/useAtom.d.ts.map +0 -1
- package/dist/useAtom.js +0 -38
- package/dist/useAtom.js.map +0 -1
- package/dist/useRow.d.ts +0 -50
- package/dist/useRow.d.ts.map +0 -1
- package/dist/useRow.js +0 -93
- package/dist/useRow.js.map +0 -1
- package/dist/useRow.test.d.ts +0 -2
- package/dist/useRow.test.d.ts.map +0 -1
- package/dist/useRow.test.js +0 -202
- package/dist/useRow.test.js.map +0 -1
- package/dist/useScopedQuery.d.ts +0 -33
- package/dist/useScopedQuery.d.ts.map +0 -1
- package/dist/useScopedQuery.js +0 -87
- package/dist/useScopedQuery.js.map +0 -1
- package/dist/useScopedQuery.test.d.ts +0 -2
- package/dist/useScopedQuery.test.d.ts.map +0 -1
- package/dist/useScopedQuery.test.js +0 -60
- package/dist/useScopedQuery.test.js.map +0 -1
- package/src/__snapshots__/useRow.test.tsx.snap +0 -360
- package/src/useAtom.ts +0 -52
- package/src/useRow.test.tsx +0 -344
- package/src/useRow.ts +0 -188
- package/src/useScopedQuery.test.tsx +0 -96
- package/src/useScopedQuery.ts +0 -143
- package/tsconfig.json +0 -20
- package/vitest.config.js +0 -17
@@ -1,9 +1,9 @@
|
|
1
|
+
import { makeInMemoryAdapter } from '@livestore/adapter-web'
|
1
2
|
import { provideOtel } from '@livestore/common'
|
2
|
-
import {
|
3
|
-
import type {
|
4
|
-
import { createStore
|
5
|
-
import { Effect } from '@livestore/utils/effect'
|
6
|
-
import { makeInMemoryAdapter } from '@livestore/web'
|
3
|
+
import { Events, makeSchema, State } from '@livestore/common/schema'
|
4
|
+
import type { Store } from '@livestore/livestore'
|
5
|
+
import { createStore } from '@livestore/livestore'
|
6
|
+
import { Effect, Schema } from '@livestore/utils/effect'
|
7
7
|
import type * as otel from '@opentelemetry/api'
|
8
8
|
import React from 'react'
|
9
9
|
|
@@ -22,55 +22,78 @@ export type AppState = {
|
|
22
22
|
filter: Filter
|
23
23
|
}
|
24
24
|
|
25
|
-
|
26
|
-
'todos',
|
27
|
-
{
|
28
|
-
id:
|
29
|
-
text:
|
30
|
-
completed:
|
25
|
+
const todos = State.SQLite.table({
|
26
|
+
name: 'todos',
|
27
|
+
columns: {
|
28
|
+
id: State.SQLite.text({ primaryKey: true }),
|
29
|
+
text: State.SQLite.text({ default: '', nullable: false }),
|
30
|
+
completed: State.SQLite.boolean({ default: false, nullable: false }),
|
31
31
|
},
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
filter: DbSchema.text({ default: 'all', nullable: false }),
|
32
|
+
})
|
33
|
+
|
34
|
+
const app = State.SQLite.table({
|
35
|
+
name: 'app',
|
36
|
+
columns: {
|
37
|
+
id: State.SQLite.text({ primaryKey: true, default: 'static' }),
|
38
|
+
newTodoText: State.SQLite.text({ default: '', nullable: true }),
|
39
|
+
filter: State.SQLite.text({ default: 'all', nullable: false }),
|
41
40
|
},
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
41
|
+
})
|
42
|
+
|
43
|
+
const userInfo = State.SQLite.clientDocument({
|
44
|
+
name: 'UserInfo',
|
45
|
+
schema: Schema.Struct({
|
46
|
+
username: Schema.String,
|
47
|
+
text: Schema.String,
|
48
|
+
}),
|
49
|
+
default: { value: { username: '', text: '' } },
|
50
|
+
})
|
51
|
+
|
52
|
+
const AppRouterSchema = State.SQLite.clientDocument({
|
53
|
+
name: 'AppRouter',
|
54
|
+
schema: Schema.Struct({
|
55
|
+
currentTaskId: Schema.String.pipe(Schema.NullOr),
|
56
|
+
}),
|
57
|
+
default: {
|
58
|
+
value: { currentTaskId: null },
|
59
|
+
id: 'singleton',
|
50
60
|
},
|
51
|
-
|
52
|
-
|
61
|
+
})
|
62
|
+
|
63
|
+
export const events = {
|
64
|
+
todoCreated: Events.synced({
|
65
|
+
name: 'todoCreated',
|
66
|
+
schema: Schema.Struct({ id: Schema.String, text: Schema.String, completed: Schema.Boolean }),
|
67
|
+
}),
|
68
|
+
todoUpdated: Events.synced({
|
69
|
+
name: 'todoUpdated',
|
70
|
+
schema: Schema.Struct({
|
71
|
+
id: Schema.String,
|
72
|
+
text: Schema.String.pipe(Schema.optional),
|
73
|
+
completed: Schema.Boolean.pipe(Schema.optional),
|
74
|
+
}),
|
75
|
+
}),
|
76
|
+
AppRouterSet: AppRouterSchema.set,
|
77
|
+
UserInfoSet: userInfo.set,
|
78
|
+
}
|
53
79
|
|
54
|
-
|
55
|
-
|
56
|
-
{
|
57
|
-
|
58
|
-
},
|
59
|
-
{ isSingleton: true, deriveMutations: true },
|
60
|
-
)
|
80
|
+
const materializers = State.SQLite.materializers(events, {
|
81
|
+
todoCreated: ({ id, text, completed }) => todos.insert({ id, text, completed }),
|
82
|
+
todoUpdated: ({ id, text, completed }) => todos.update({ completed, text }).where({ id }),
|
83
|
+
})
|
61
84
|
|
62
|
-
export const tables = { todos, app,
|
63
|
-
|
85
|
+
export const tables = { todos, app, userInfo, AppRouterSchema }
|
86
|
+
|
87
|
+
const state = State.SQLite.makeState({ tables, materializers })
|
88
|
+
export const schema = makeSchema({ state, events })
|
64
89
|
|
65
90
|
export const makeTodoMvcReact = ({
|
66
91
|
otelTracer,
|
67
92
|
otelContext,
|
68
|
-
useGlobalReactivityGraph = true,
|
69
93
|
strictMode,
|
70
94
|
}: {
|
71
95
|
otelTracer?: otel.Tracer
|
72
96
|
otelContext?: otel.Context
|
73
|
-
useGlobalReactivityGraph?: boolean
|
74
97
|
strictMode?: boolean
|
75
98
|
} = {}) =>
|
76
99
|
Effect.gen(function* () {
|
@@ -89,18 +112,20 @@ export const makeTodoMvcReact = ({
|
|
89
112
|
}
|
90
113
|
}
|
91
114
|
|
92
|
-
const
|
93
|
-
|
94
|
-
const store = yield* createStore({
|
115
|
+
const store: Store<any> = yield* createStore({
|
95
116
|
schema,
|
96
117
|
storeId: 'default',
|
97
118
|
adapter: makeInMemoryAdapter(),
|
98
|
-
reactivityGraph,
|
99
119
|
debug: { instanceId: 'test' },
|
100
120
|
})
|
101
121
|
|
122
|
+
const storeWithReactApi = LiveStoreReact.withReactApi(store)
|
123
|
+
|
102
124
|
// TODO improve typing of `LiveStoreContext`
|
103
|
-
const storeContext = {
|
125
|
+
const storeContext = {
|
126
|
+
stage: 'running' as const,
|
127
|
+
store: storeWithReactApi,
|
128
|
+
}
|
104
129
|
|
105
130
|
const MaybeStrictMode = strictMode ? React.StrictMode : React.Fragment
|
106
131
|
|
@@ -112,5 +137,7 @@ export const makeTodoMvcReact = ({
|
|
112
137
|
</MaybeStrictMode>
|
113
138
|
)
|
114
139
|
|
115
|
-
|
140
|
+
const renderCount = makeRenderCount()
|
141
|
+
|
142
|
+
return { wrapper, store: storeWithReactApi, renderCount }
|
116
143
|
}).pipe(provideOtel({ parentSpanContext: otelContext, otelTracer }))
|
@@ -1,9 +1,8 @@
|
|
1
|
-
import type {
|
1
|
+
import type { LiveQueryDef } from '@livestore/livestore'
|
2
2
|
import { computed } from '@livestore/livestore'
|
3
3
|
import React from 'react'
|
4
4
|
|
5
5
|
import { useQuery } from '../../useQuery.js'
|
6
|
-
import { useScopedQuery } from '../../useScopedQuery.js'
|
7
6
|
|
8
7
|
/*
|
9
8
|
TODO:
|
@@ -12,7 +11,7 @@ TODO:
|
|
12
11
|
*/
|
13
12
|
|
14
13
|
export type LiveListProps<TItem> = {
|
15
|
-
items$:
|
14
|
+
items$: LiveQueryDef<ReadonlyArray<TItem>>
|
16
15
|
// TODO refactor render-flag to allow for transition animations on add/remove
|
17
16
|
renderItem: (item: TItem, opts: { index: number; isInitialListRender: boolean }) => React.ReactNode
|
18
17
|
/** Needs to be unique across all list items */
|
@@ -32,14 +31,18 @@ export const LiveList = <TItem,>({ items$, renderItem, getKey }: LiveListProps<T
|
|
32
31
|
|
33
32
|
React.useEffect(() => setHasMounted(true), [])
|
34
33
|
|
35
|
-
const
|
36
|
-
const keys = useScopedQuery(keysCb, 'fixed')
|
34
|
+
const keys = useQuery(computed((get) => get(items$).map(getKey)))
|
37
35
|
const arr = React.useMemo(
|
38
36
|
() =>
|
39
37
|
keys.map(
|
40
38
|
(key) =>
|
41
39
|
// TODO figure out a way so that `item$` returns an ordered lookup map to more efficiently find the item by key
|
42
|
-
[
|
40
|
+
[
|
41
|
+
key,
|
42
|
+
computed((get) => get(items$).find((item) => getKey(item, 0) === key)!, {
|
43
|
+
deps: [key],
|
44
|
+
}) as LiveQueryDef<TItem>,
|
45
|
+
] as const,
|
43
46
|
),
|
44
47
|
[getKey, items$, keys],
|
45
48
|
)
|
@@ -65,7 +68,7 @@ const ItemWrapper = <TItem,>({
|
|
65
68
|
renderItem,
|
66
69
|
}: {
|
67
70
|
itemKey: string | number
|
68
|
-
item$:
|
71
|
+
item$: LiveQueryDef<TItem>
|
69
72
|
opts: { index: number; isInitialListRender: boolean }
|
70
73
|
renderItem: (item: TItem, opts: { index: number; isInitialListRender: boolean }) => React.ReactNode
|
71
74
|
}) => {
|
package/src/mod.ts
CHANGED
@@ -1,13 +1,12 @@
|
|
1
|
-
export { LiveStoreContext,
|
1
|
+
export { LiveStoreContext, type ReactApi } from './LiveStoreContext.js'
|
2
|
+
export { useStore, withReactApi } from './useStore.js'
|
2
3
|
export { LiveStoreProvider } from './LiveStoreProvider.js'
|
3
|
-
export { useQuery } from './useQuery.js'
|
4
|
-
export { useScopedQuery } from './useScopedQuery.js'
|
5
4
|
export { useStackInfo } from './utils/stack-info.js'
|
5
|
+
export { useQuery, useQueryRef } from './useQuery.js'
|
6
6
|
export {
|
7
|
-
|
7
|
+
useClientDocument,
|
8
8
|
type StateSetters,
|
9
9
|
type SetStateAction,
|
10
10
|
type Dispatch,
|
11
11
|
type UseRowResult as UseStateResult,
|
12
|
-
} from './
|
13
|
-
export { useAtom } from './useAtom.js'
|
12
|
+
} from './useClientDocument.js'
|
@@ -0,0 +1,306 @@
|
|
1
|
+
import * as LiveStore from '@livestore/livestore'
|
2
|
+
import { getSimplifiedRootSpan } from '@livestore/livestore/internal/testing-utils'
|
3
|
+
import { Effect, ReadonlyRecord, Schema } from '@livestore/utils/effect'
|
4
|
+
import { Vitest } from '@livestore/utils-dev/node-vitest'
|
5
|
+
import * as otel from '@opentelemetry/api'
|
6
|
+
import { BasicTracerProvider, InMemorySpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'
|
7
|
+
import * as ReactTesting from '@testing-library/react'
|
8
|
+
import React from 'react'
|
9
|
+
import { beforeEach, expect, it } from 'vitest'
|
10
|
+
|
11
|
+
import { events, makeTodoMvcReact, tables } from './__tests__/fixture.js'
|
12
|
+
import type * as LiveStoreReact from './mod.js'
|
13
|
+
import { __resetUseRcResourceCache } from './useRcResource.js'
|
14
|
+
|
15
|
+
// const strictMode = process.env.REACT_STRICT_MODE !== undefined
|
16
|
+
|
17
|
+
// NOTE running tests concurrently doesn't work with the default global db graph
|
18
|
+
Vitest.describe('useClientDocument', () => {
|
19
|
+
beforeEach(() => {
|
20
|
+
__resetUseRcResourceCache()
|
21
|
+
})
|
22
|
+
|
23
|
+
Vitest.scopedLive('should update the data based on component key', () =>
|
24
|
+
Effect.gen(function* () {
|
25
|
+
const { wrapper, store, renderCount } = yield* makeTodoMvcReact({})
|
26
|
+
|
27
|
+
const { result, rerender } = ReactTesting.renderHook(
|
28
|
+
(userId: string) => {
|
29
|
+
renderCount.inc()
|
30
|
+
|
31
|
+
const [state, setState, id] = store.useClientDocument(tables.userInfo, userId)
|
32
|
+
return { state, setState, id }
|
33
|
+
},
|
34
|
+
{ wrapper, initialProps: 'u1' },
|
35
|
+
)
|
36
|
+
|
37
|
+
expect(result.current.id).toBe('u1')
|
38
|
+
expect(result.current.state.username).toBe('')
|
39
|
+
expect(renderCount.val).toBe(1)
|
40
|
+
expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
|
41
|
+
store.commit(tables.userInfo.set({ username: 'username_u2' }, 'u2'))
|
42
|
+
|
43
|
+
rerender('u2')
|
44
|
+
|
45
|
+
expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
|
46
|
+
expect(result.current.id).toBe('u2')
|
47
|
+
expect(result.current.state.username).toBe('username_u2')
|
48
|
+
expect(renderCount.val).toBe(2)
|
49
|
+
}),
|
50
|
+
)
|
51
|
+
|
52
|
+
// TODO add a test that makes sure React doesn't re-render when a setter is used to set the same value
|
53
|
+
|
54
|
+
Vitest.scopedLive('should update the data reactively - via setState', () =>
|
55
|
+
Effect.gen(function* () {
|
56
|
+
const { wrapper, store, renderCount } = yield* makeTodoMvcReact({})
|
57
|
+
|
58
|
+
const { result } = ReactTesting.renderHook(
|
59
|
+
(userId: string) => {
|
60
|
+
renderCount.inc()
|
61
|
+
|
62
|
+
const [state, setState, id] = store.useClientDocument(tables.userInfo, userId)
|
63
|
+
return { state, setState, id }
|
64
|
+
},
|
65
|
+
{ wrapper, initialProps: 'u1' },
|
66
|
+
)
|
67
|
+
|
68
|
+
expect(result.current.id).toBe('u1')
|
69
|
+
expect(result.current.state.username).toBe('')
|
70
|
+
expect(renderCount.val).toBe(1)
|
71
|
+
|
72
|
+
ReactTesting.act(() => result.current.setState({ username: 'username_u1_hello' }))
|
73
|
+
|
74
|
+
expect(result.current.id).toBe('u1')
|
75
|
+
expect(result.current.state.username).toBe('username_u1_hello')
|
76
|
+
expect(renderCount.val).toBe(2)
|
77
|
+
}),
|
78
|
+
)
|
79
|
+
|
80
|
+
Vitest.scopedLive('should update the data reactively - via raw store commit', () =>
|
81
|
+
Effect.gen(function* () {
|
82
|
+
const { wrapper, store, renderCount } = yield* makeTodoMvcReact({})
|
83
|
+
|
84
|
+
const { result } = ReactTesting.renderHook(
|
85
|
+
(userId: string) => {
|
86
|
+
renderCount.inc()
|
87
|
+
|
88
|
+
const [state, setState, id] = store.useClientDocument(tables.userInfo, userId)
|
89
|
+
return { state, setState, id }
|
90
|
+
},
|
91
|
+
{ wrapper, initialProps: 'u1' },
|
92
|
+
)
|
93
|
+
|
94
|
+
expect(result.current.id).toBe('u1')
|
95
|
+
expect(result.current.state.username).toBe('')
|
96
|
+
expect(renderCount.val).toBe(1)
|
97
|
+
|
98
|
+
ReactTesting.act(() => store.commit(events.UserInfoSet({ username: 'username_u1_hello' }, 'u1')))
|
99
|
+
|
100
|
+
expect(result.current.id).toBe('u1')
|
101
|
+
expect(result.current.state.username).toBe('username_u1_hello')
|
102
|
+
expect(renderCount.val).toBe(2)
|
103
|
+
}),
|
104
|
+
)
|
105
|
+
|
106
|
+
Vitest.scopedLive('should work for a larger app', () =>
|
107
|
+
Effect.gen(function* () {
|
108
|
+
const { wrapper, store, renderCount } = yield* makeTodoMvcReact({})
|
109
|
+
|
110
|
+
const allTodos$ = LiveStore.queryDb(
|
111
|
+
{ query: `select * from todos`, schema: Schema.Array(tables.todos.rowSchema) },
|
112
|
+
{ label: 'allTodos' },
|
113
|
+
)
|
114
|
+
|
115
|
+
let globalSetState: LiveStoreReact.StateSetters<typeof tables.AppRouterSchema> | undefined
|
116
|
+
const AppRouter: React.FC = () => {
|
117
|
+
renderCount.inc()
|
118
|
+
|
119
|
+
const [state, setState] = store.useClientDocument(tables.AppRouterSchema, 'singleton')
|
120
|
+
|
121
|
+
globalSetState = setState
|
122
|
+
|
123
|
+
return (
|
124
|
+
<div>
|
125
|
+
<TasksList setTaskId={(taskId) => setState({ currentTaskId: taskId })} />
|
126
|
+
<div role="current-id">Current Task Id: {state.currentTaskId ?? '-'}</div>
|
127
|
+
{state.currentTaskId ? <TaskDetails id={state.currentTaskId} /> : <div>Click on a task to see details</div>}
|
128
|
+
</div>
|
129
|
+
)
|
130
|
+
}
|
131
|
+
|
132
|
+
const TasksList: React.FC<{ setTaskId: (_: string) => void }> = ({ setTaskId }) => {
|
133
|
+
const allTodos = store.useQuery(allTodos$)
|
134
|
+
|
135
|
+
return (
|
136
|
+
<div>
|
137
|
+
{allTodos.map((_) => (
|
138
|
+
<div key={_.id} onClick={() => setTaskId(_.id)}>
|
139
|
+
{_.id}
|
140
|
+
</div>
|
141
|
+
))}
|
142
|
+
</div>
|
143
|
+
)
|
144
|
+
}
|
145
|
+
|
146
|
+
const TaskDetails: React.FC<{ id: string }> = ({ id }) => {
|
147
|
+
const todo = store.useQuery(LiveStore.queryDb(tables.todos.where({ id }).first(), { deps: id }))
|
148
|
+
return <div role="content">{JSON.stringify(todo)}</div>
|
149
|
+
}
|
150
|
+
|
151
|
+
const renderResult = ReactTesting.render(<AppRouter />, { wrapper })
|
152
|
+
|
153
|
+
expect(renderCount.val).toBe(1)
|
154
|
+
|
155
|
+
ReactTesting.act(() =>
|
156
|
+
store.commit(
|
157
|
+
LiveStore.rawSqlEvent({
|
158
|
+
sql: LiveStore.sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)`,
|
159
|
+
}),
|
160
|
+
),
|
161
|
+
)
|
162
|
+
|
163
|
+
expect(renderCount.val).toBe(1)
|
164
|
+
expect(renderResult.getByRole('current-id').innerHTML).toMatchInlineSnapshot('"Current Task Id: -"')
|
165
|
+
|
166
|
+
ReactTesting.act(() => globalSetState!({ currentTaskId: 't1' }))
|
167
|
+
|
168
|
+
expect(renderCount.val).toBe(2)
|
169
|
+
expect(renderResult.getByRole('content').innerHTML).toMatchInlineSnapshot(
|
170
|
+
`"{"id":"t1","text":"buy milk","completed":false}"`,
|
171
|
+
)
|
172
|
+
|
173
|
+
expect(renderResult.getByRole('current-id').innerHTML).toMatchInlineSnapshot('"Current Task Id: t1"')
|
174
|
+
|
175
|
+
ReactTesting.act(() =>
|
176
|
+
store.commit(
|
177
|
+
events.todoCreated({ id: 't2', text: 'buy eggs', completed: false }),
|
178
|
+
events.AppRouterSet({ currentTaskId: 't2' }),
|
179
|
+
events.todoCreated({ id: 't3', text: 'buy bread', completed: false }),
|
180
|
+
),
|
181
|
+
)
|
182
|
+
|
183
|
+
expect(renderCount.val).toBe(3)
|
184
|
+
expect(renderResult.getByRole('current-id').innerHTML).toMatchInlineSnapshot('"Current Task Id: t2"')
|
185
|
+
}),
|
186
|
+
)
|
187
|
+
|
188
|
+
Vitest.scopedLive('should work for a useClientDocument query chained with a useTemporary query', () =>
|
189
|
+
Effect.gen(function* () {
|
190
|
+
const { store, wrapper, renderCount } = yield* makeTodoMvcReact({})
|
191
|
+
|
192
|
+
store.commit(
|
193
|
+
events.todoCreated({ id: 't1', text: 'buy milk', completed: false }),
|
194
|
+
events.todoCreated({ id: 't2', text: 'buy bread', completed: false }),
|
195
|
+
)
|
196
|
+
|
197
|
+
const { result, unmount, rerender } = ReactTesting.renderHook(
|
198
|
+
(userId: string) => {
|
199
|
+
renderCount.inc()
|
200
|
+
|
201
|
+
const [_row, _setRow, _id, rowState$] = store.useClientDocument(tables.userInfo, userId)
|
202
|
+
const todos = store.useQuery(
|
203
|
+
LiveStore.queryDb(
|
204
|
+
(get) => tables.todos.where('text', 'LIKE', `%${get(rowState$).text}%`),
|
205
|
+
// TODO find a way where explicit `userId` is not needed here
|
206
|
+
// possibly by automatically understanding the `get(rowState$)` dependency
|
207
|
+
{ label: 'todosFiltered', deps: userId },
|
208
|
+
),
|
209
|
+
// TODO introduce a `deps` array which is only needed when a query is parametric
|
210
|
+
)
|
211
|
+
|
212
|
+
return { todos }
|
213
|
+
},
|
214
|
+
{ wrapper, initialProps: 'u1' },
|
215
|
+
)
|
216
|
+
|
217
|
+
ReactTesting.act(() => store.commit(events.UserInfoSet({ username: 'username_u2', text: 'milk' }, 'u2')))
|
218
|
+
|
219
|
+
expect(result.current.todos.length).toBe(2)
|
220
|
+
expect(renderCount.val).toBe(1)
|
221
|
+
|
222
|
+
rerender('u2')
|
223
|
+
|
224
|
+
expect(result.current.todos.length).toBe(1)
|
225
|
+
expect(renderCount.val).toBe(2)
|
226
|
+
|
227
|
+
unmount()
|
228
|
+
}),
|
229
|
+
)
|
230
|
+
|
231
|
+
Vitest.describe('otel', () => {
|
232
|
+
it.each([{ strictMode: true }, { strictMode: false }])(
|
233
|
+
'should update the data based on component key strictMode=%s',
|
234
|
+
async ({ strictMode }) => {
|
235
|
+
const exporter = new InMemorySpanExporter()
|
236
|
+
|
237
|
+
const provider = new BasicTracerProvider({
|
238
|
+
spanProcessors: [new SimpleSpanProcessor(exporter)],
|
239
|
+
})
|
240
|
+
|
241
|
+
const otelTracer = provider.getTracer(`testing-${strictMode ? 'strict' : 'non-strict'}`)
|
242
|
+
|
243
|
+
const span = otelTracer.startSpan('test-root')
|
244
|
+
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
245
|
+
|
246
|
+
await Effect.gen(function* () {
|
247
|
+
const { wrapper, store, renderCount } = yield* makeTodoMvcReact({
|
248
|
+
otelContext,
|
249
|
+
otelTracer,
|
250
|
+
strictMode,
|
251
|
+
})
|
252
|
+
|
253
|
+
const { result, rerender, unmount } = ReactTesting.renderHook(
|
254
|
+
(userId: string) => {
|
255
|
+
renderCount.inc()
|
256
|
+
|
257
|
+
const [state, setState, id] = store.useClientDocument(tables.userInfo, userId)
|
258
|
+
return { state, setState, id }
|
259
|
+
},
|
260
|
+
{ wrapper, initialProps: 'u1' },
|
261
|
+
)
|
262
|
+
|
263
|
+
expect(result.current.id).toBe('u1')
|
264
|
+
expect(result.current.state.username).toBe('')
|
265
|
+
expect(renderCount.val).toBe(1)
|
266
|
+
|
267
|
+
// For u2 we'll make sure that the row already exists,
|
268
|
+
// so the lazy `insert` will be skipped
|
269
|
+
ReactTesting.act(() => store.commit(events.UserInfoSet({ username: 'username_u2' }, 'u2')))
|
270
|
+
|
271
|
+
rerender('u2')
|
272
|
+
|
273
|
+
expect(result.current.id).toBe('u2')
|
274
|
+
expect(result.current.state.username).toBe('username_u2')
|
275
|
+
expect(renderCount.val).toBe(2)
|
276
|
+
|
277
|
+
unmount()
|
278
|
+
span.end()
|
279
|
+
}).pipe(Effect.scoped, Effect.tapCauseLogPretty, Effect.runPromise)
|
280
|
+
|
281
|
+
await provider.forceFlush()
|
282
|
+
|
283
|
+
const mapAttributes = (attributes: otel.Attributes) => {
|
284
|
+
return ReadonlyRecord.map(attributes, (val, key) => {
|
285
|
+
if (key === 'firstStackInfo') {
|
286
|
+
const stackInfo = JSON.parse(val as string) as LiveStore.StackInfo
|
287
|
+
// stackInfo.frames.shift() // Removes `renderHook.wrapper` from the stack
|
288
|
+
stackInfo.frames.forEach((_) => {
|
289
|
+
if (_.name.includes('renderHook.wrapper')) {
|
290
|
+
_.name = 'renderHook.wrapper'
|
291
|
+
}
|
292
|
+
_.filePath = '__REPLACED_FOR_SNAPSHOT__'
|
293
|
+
})
|
294
|
+
return JSON.stringify(stackInfo)
|
295
|
+
}
|
296
|
+
return val
|
297
|
+
})
|
298
|
+
}
|
299
|
+
|
300
|
+
expect(getSimplifiedRootSpan(exporter, mapAttributes)).toMatchSnapshot()
|
301
|
+
|
302
|
+
await provider.shutdown()
|
303
|
+
},
|
304
|
+
)
|
305
|
+
})
|
306
|
+
})
|