@dhis2/app-service-data 3.17.1 → 3.18.0-beta.2

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.
Files changed (38) hide show
  1. package/README.md +104 -0
  2. package/build/cjs/react/hooks/useDataQuery.js +4 -0
  3. package/build/es/react/hooks/useDataQuery.js +5 -0
  4. package/build/types/react/hooks/useDataQuery.d.ts +6 -2
  5. package/package.json +5 -4
  6. package/.gitignore +0 -5
  7. package/d2.config.js +0 -9
  8. package/jest.config.js +0 -14
  9. package/src/__tests__/integration.test.tsx +0 -80
  10. package/src/__tests__/mutations.test.tsx +0 -71
  11. package/src/index.ts +0 -5
  12. package/src/react/components/CustomDataProvider.tsx +0 -33
  13. package/src/react/components/DataMutation.tsx +0 -24
  14. package/src/react/components/DataProvider.test.tsx +0 -22
  15. package/src/react/components/DataProvider.tsx +0 -58
  16. package/src/react/components/DataQuery.tsx +0 -26
  17. package/src/react/components/index.ts +0 -4
  18. package/src/react/context/DataContext.tsx +0 -5
  19. package/src/react/context/defaultDataContext.test.ts +0 -46
  20. package/src/react/context/defaultDataContext.ts +0 -9
  21. package/src/react/hooks/index.ts +0 -3
  22. package/src/react/hooks/mergeAndCompareVariables.test.ts +0 -65
  23. package/src/react/hooks/mergeAndCompareVariables.ts +0 -32
  24. package/src/react/hooks/stableVariablesHash.test.ts +0 -53
  25. package/src/react/hooks/stableVariablesHash.ts +0 -56
  26. package/src/react/hooks/useDataEngine.ts +0 -8
  27. package/src/react/hooks/useDataMutation.test.tsx +0 -296
  28. package/src/react/hooks/useDataMutation.ts +0 -42
  29. package/src/react/hooks/useDataQuery.test.tsx +0 -1029
  30. package/src/react/hooks/useDataQuery.ts +0 -170
  31. package/src/react/hooks/useQueryExecutor.test.tsx +0 -195
  32. package/src/react/hooks/useQueryExecutor.ts +0 -99
  33. package/src/react/hooks/useStaticInput.test.ts +0 -101
  34. package/src/react/hooks/useStaticInput.ts +0 -27
  35. package/src/react/index.ts +0 -2
  36. package/src/setupRTL.ts +0 -5
  37. package/src/types.ts +0 -72
  38. package/tsconfig.json +0 -10
@@ -1,65 +0,0 @@
1
- import { mergeAndCompareVariables } from './mergeAndCompareVariables'
2
- import { stableVariablesHash } from './stableVariablesHash'
3
- jest.mock('./stableVariablesHash', () => ({
4
- stableVariablesHash: (object: unknown) => JSON.stringify(object),
5
- }))
6
-
7
- const testVariables = {
8
- question: 'What do you get when you multiply six by nine?',
9
- answer: 42,
10
- }
11
- const testHash = stableVariablesHash(testVariables)
12
-
13
- describe('mergeAndCompareVariables', () => {
14
- it('Should return previous variables and hash when no new variables are provided', () => {
15
- expect(
16
- mergeAndCompareVariables(testVariables, undefined, undefined)
17
- ).toMatchObject({
18
- identical: true,
19
- mergedVariables: testVariables,
20
- mergedVariablesHash: undefined,
21
- })
22
- })
23
-
24
- it('Should return identical: true when merged variables are identical to old variables (without prev hash)', () => {
25
- const newVariables = { answer: testVariables.answer }
26
-
27
- expect(
28
- mergeAndCompareVariables(testVariables, newVariables, undefined)
29
- ).toMatchObject({
30
- identical: true,
31
- mergedVariables: testVariables,
32
- mergedVariablesHash: testHash,
33
- })
34
- })
35
-
36
- it('Should return identical: false with incorrect previous hash', () => {
37
- const incorrectPreviousHash = 'IAmAHash'
38
- const newVariables = { answer: 42 }
39
-
40
- expect(
41
- mergeAndCompareVariables(
42
- testVariables,
43
- newVariables,
44
- incorrectPreviousHash
45
- )
46
- ).toMatchObject({
47
- identical: false,
48
- mergedVariables: testVariables,
49
- mergedVariablesHash: testHash,
50
- })
51
- })
52
-
53
- it('Should return identical: false when merged variables are different than old variables', () => {
54
- const newVariables = { answer: 43 }
55
- const expectedMergedVariables = { ...testVariables, ...newVariables }
56
-
57
- expect(
58
- mergeAndCompareVariables(testVariables, newVariables, testHash)
59
- ).toMatchObject({
60
- identical: false,
61
- mergedVariables: expectedMergedVariables,
62
- mergedVariablesHash: stableVariablesHash(expectedMergedVariables),
63
- })
64
- })
65
- })
@@ -1,32 +0,0 @@
1
- import type { QueryVariables } from '@dhis2/data-engine'
2
- import { stableVariablesHash } from './stableVariablesHash'
3
-
4
- export const mergeAndCompareVariables = (
5
- previousVariables?: QueryVariables,
6
- newVariables?: QueryVariables,
7
- previousHash?: string
8
- ) => {
9
- if (!newVariables) {
10
- return {
11
- identical: true,
12
- mergedVariablesHash: previousHash,
13
- mergedVariables: previousVariables,
14
- }
15
- }
16
-
17
- // Use cached hash if it exists
18
- const currentHash = previousHash || stableVariablesHash(previousVariables)
19
-
20
- const mergedVariables = {
21
- ...previousVariables,
22
- ...newVariables,
23
- }
24
- const mergedVariablesHash = stableVariablesHash(mergedVariables)
25
- const identical = currentHash === mergedVariablesHash
26
-
27
- return {
28
- identical,
29
- mergedVariablesHash,
30
- mergedVariables,
31
- }
32
- }
@@ -1,53 +0,0 @@
1
- import { stableVariablesHash } from './stableVariablesHash'
2
-
3
- describe('stableVariablesHash', () => {
4
- it('sorts objects before hashing', () => {
5
- const one = {
6
- a: {
7
- one: 1,
8
- two: 2,
9
- three: 3,
10
- },
11
- b: [1, 2, 3],
12
- c: 'c',
13
- }
14
- const two = {
15
- c: 'c',
16
- b: [1, 2, 3],
17
- a: {
18
- three: 3,
19
- two: 2,
20
- one: 1,
21
- },
22
- }
23
-
24
- expect(stableVariablesHash(one)).toEqual(stableVariablesHash(two))
25
- })
26
-
27
- it('can handle primitives', () => {
28
- const one = undefined
29
- const two = 'string'
30
- const three = 3
31
- const four = null
32
- const five = true
33
-
34
- expect(stableVariablesHash(one)).toMatchInlineSnapshot(`undefined`)
35
- expect(stableVariablesHash(two)).toMatchInlineSnapshot(`"\\"string\\""`)
36
- expect(stableVariablesHash(three)).toMatchInlineSnapshot(`"3"`)
37
- expect(stableVariablesHash(four)).toMatchInlineSnapshot(`"null"`)
38
- expect(stableVariablesHash(five)).toMatchInlineSnapshot(`"true"`)
39
- })
40
-
41
- it('throws a clear error when the variables contain a circular reference', () => {
42
- const unserializable: any = {
43
- value: 'value',
44
- }
45
- unserializable.circular = unserializable
46
-
47
- expect(() =>
48
- stableVariablesHash(unserializable)
49
- ).toThrowErrorMatchingInlineSnapshot(
50
- `"Could not serialize variables. Make sure that the variables do not contain circular references and can be processed by JSON.stringify."`
51
- )
52
- })
53
- })
@@ -1,56 +0,0 @@
1
- function hasObjectPrototype(o: any): boolean {
2
- return Object.prototype.toString.call(o) === '[object Object]'
3
- }
4
-
5
- export function isPlainObject(o: any): o is object {
6
- if (!hasObjectPrototype(o)) {
7
- return false
8
- }
9
-
10
- // If has modified constructor
11
- const ctor = o.constructor
12
- if (typeof ctor === 'undefined') {
13
- return true
14
- }
15
-
16
- // If has modified prototype
17
- const prot = ctor.prototype
18
- if (!hasObjectPrototype(prot)) {
19
- return false
20
- }
21
-
22
- // If constructor does not have an Object-specific method
23
- if (!Object.prototype.hasOwnProperty.call(prot, 'isPrototypeOf')) {
24
- return false
25
- }
26
-
27
- // Most likely a plain Object
28
- return true
29
- }
30
-
31
- /**
32
- * Hashes the value into a stable hash.
33
- */
34
-
35
- export function stableVariablesHash(value: any): string {
36
- let hash
37
-
38
- try {
39
- hash = JSON.stringify(value, (_, val) =>
40
- isPlainObject(val)
41
- ? Object.keys(val)
42
- .sort()
43
- .reduce((result: Record<string, unknown>, key) => {
44
- result[key] = (val as Record<string, unknown>)[key]
45
- return result
46
- }, {})
47
- : val
48
- )
49
- } catch (e) {
50
- throw new Error(
51
- 'Could not serialize variables. Make sure that the variables do not contain circular references and can be processed by JSON.stringify.'
52
- )
53
- }
54
-
55
- return hash
56
- }
@@ -1,8 +0,0 @@
1
- import { useContext } from 'react'
2
- import { DataContext } from '../context/DataContext'
3
-
4
- export const useDataEngine = () => {
5
- const context = useContext(DataContext)
6
-
7
- return context.engine
8
- }
@@ -1,296 +0,0 @@
1
- import type {
2
- CreateMutation,
3
- UpdateMutation,
4
- QueryVariables,
5
- } from '@dhis2/data-engine'
6
- import { renderHook, act, waitFor } from '@testing-library/react'
7
- import * as React from 'react'
8
- import { CustomDataProvider } from '../components/CustomDataProvider'
9
- import { useDataEngine } from './useDataEngine'
10
- import { useDataMutation } from './useDataMutation'
11
-
12
- describe('useDataMutation', () => {
13
- it('should render without failing', async () => {
14
- const mutation: CreateMutation = {
15
- type: 'create',
16
- resource: 'answer',
17
- data: { answer: '?' },
18
- }
19
- const data = { answer: 42 }
20
- const wrapper = ({ children }: { children?: React.ReactNode }) => (
21
- <CustomDataProvider data={data}>{children}</CustomDataProvider>
22
- )
23
-
24
- const { result } = renderHook(() => useDataMutation(mutation), {
25
- wrapper,
26
- })
27
-
28
- const [mutate, beforeMutation] = result.current
29
- expect(beforeMutation).toMatchObject({
30
- loading: false,
31
- called: false,
32
- })
33
-
34
- act(() => {
35
- mutate()
36
- })
37
-
38
- await waitFor(() => {
39
- const [, duringMutation] = result.current
40
- expect(duringMutation).toMatchObject({
41
- loading: true,
42
- called: true,
43
- })
44
- })
45
-
46
- await waitFor(() => {
47
- const [, afterMutation] = result.current
48
- expect(afterMutation).toMatchObject({
49
- loading: false,
50
- called: true,
51
- data: 42,
52
- })
53
- })
54
- })
55
-
56
- it('should run immediately with lazy: false', async () => {
57
- const mutation: CreateMutation = {
58
- type: 'create',
59
- resource: 'answer',
60
- data: { answer: '?' },
61
- }
62
- const data = { answer: 42 }
63
- const wrapper = ({ children }: { children?: React.ReactNode }) => (
64
- <CustomDataProvider data={data}>{children}</CustomDataProvider>
65
- )
66
-
67
- const { result } = renderHook(
68
- () => useDataMutation(mutation, { lazy: false }),
69
- { wrapper }
70
- )
71
-
72
- const [, duringMutation] = result.current
73
- expect(duringMutation).toMatchObject({
74
- loading: true,
75
- called: true,
76
- })
77
-
78
- await waitFor(() => {
79
- const [, afterMutation] = result.current
80
- expect(afterMutation).toMatchObject({
81
- loading: false,
82
- called: true,
83
- data: 42,
84
- })
85
- })
86
- })
87
-
88
- it('should call onComplete on success', async () => {
89
- const onComplete = jest.fn()
90
- const mutation: CreateMutation = {
91
- type: 'create',
92
- resource: 'answer',
93
- data: { answer: '?' },
94
- }
95
- const data = { answer: 42 }
96
- const wrapper = ({ children }: { children?: React.ReactNode }) => (
97
- <CustomDataProvider data={data}>{children}</CustomDataProvider>
98
- )
99
-
100
- const { result } = renderHook(
101
- () => useDataMutation(mutation, { onComplete }),
102
- { wrapper }
103
- )
104
-
105
- expect(onComplete).toHaveBeenCalledTimes(0)
106
- const [mutate] = result.current
107
- act(() => {
108
- mutate()
109
- })
110
-
111
- await waitFor(() => {
112
- const [, state] = result.current
113
- expect(state).toMatchObject({
114
- loading: false,
115
- called: true,
116
- data: 42,
117
- })
118
- expect(onComplete).toHaveBeenCalledTimes(1)
119
- expect(onComplete).toHaveBeenLastCalledWith(42)
120
- })
121
- })
122
-
123
- it('should call onError on error', async () => {
124
- const error = new Error('Something went wrong')
125
- const onError = jest.fn()
126
- const mutation: CreateMutation = {
127
- type: 'create',
128
- resource: 'answer',
129
- data: { answer: 42 },
130
- }
131
- const data = {
132
- answer: () => {
133
- throw error
134
- },
135
- }
136
- const wrapper = ({ children }: { children?: React.ReactNode }) => (
137
- <CustomDataProvider data={data}>{children}</CustomDataProvider>
138
- )
139
-
140
- const { result } = renderHook(
141
- () => useDataMutation(mutation, { onError }),
142
- { wrapper }
143
- )
144
-
145
- expect(onError).toHaveBeenCalledTimes(0)
146
- const [mutate] = result.current
147
-
148
- act(() => {
149
- mutate()
150
- })
151
-
152
- await waitFor(() => {
153
- const [, state] = result.current
154
- expect(state).toMatchObject({
155
- loading: false,
156
- called: true,
157
- error,
158
- })
159
- })
160
- expect(onError).toHaveBeenCalledTimes(1)
161
- expect(onError).toHaveBeenLastCalledWith(error)
162
- })
163
-
164
- it('should resolve variables', async () => {
165
- const mutation = {
166
- type: 'update',
167
- resource: 'answer',
168
- id: ({ id }: QueryVariables) => id as string,
169
- data: { answer: '?' },
170
- } as unknown as UpdateMutation
171
- const answerSpy = jest.fn(() => Promise.resolve(42))
172
- const data = { answer: answerSpy }
173
- const wrapper = ({ children }: { children?: React.ReactNode }) => (
174
- <CustomDataProvider data={data}>{children}</CustomDataProvider>
175
- )
176
-
177
- const { result } = renderHook(
178
- () =>
179
- useDataMutation(mutation, {
180
- lazy: false,
181
- variables: { id: '1' },
182
- }),
183
- { wrapper }
184
- )
185
-
186
- await waitFor(() => {
187
- expect(answerSpy).toHaveBeenLastCalledWith(
188
- expect.any(String),
189
- expect.objectContaining({
190
- id: '1',
191
- }),
192
- expect.any(Object)
193
- )
194
- })
195
-
196
- const [mutate] = result.current
197
- act(() => {
198
- mutate({ id: '2' })
199
- })
200
-
201
- await waitFor(() => {
202
- expect(answerSpy).toHaveBeenLastCalledWith(
203
- expect.any(String),
204
- expect.objectContaining({
205
- id: '2',
206
- }),
207
- expect.any(Object)
208
- )
209
- })
210
- })
211
-
212
- it('should return a reference to the engine', async () => {
213
- const mutation: CreateMutation = {
214
- type: 'create',
215
- resource: 'answer',
216
- data: { answer: '?' },
217
- }
218
- const wrapper = ({ children }: { children?: React.ReactNode }) => (
219
- <CustomDataProvider data={{}}>{children}</CustomDataProvider>
220
- )
221
-
222
- const engineHook = renderHook(() => useDataEngine(), { wrapper })
223
- const mutationHook = renderHook(() => useDataMutation(mutation), {
224
- wrapper,
225
- })
226
-
227
- /**
228
- * Ideally we'd check referential equality here with .toBe, but since
229
- * both hooks run in a different context that doesn't work.
230
- */
231
- expect(mutationHook.result.current[1].engine).toStrictEqual(
232
- engineHook.result.current
233
- )
234
- })
235
-
236
- it('should return a stable mutate function', async () => {
237
- const mutation: CreateMutation = {
238
- type: 'create',
239
- resource: 'answer',
240
- data: { answer: '?' },
241
- }
242
- const data = { answer: 42 }
243
- const wrapper = ({ children }: { children?: React.ReactNode }) => (
244
- <CustomDataProvider data={data}>{children}</CustomDataProvider>
245
- )
246
-
247
- const { result } = renderHook(() => useDataMutation(mutation), {
248
- wrapper,
249
- })
250
-
251
- const [firstMutate] = result.current
252
-
253
- await act(async () => {
254
- await firstMutate({ variable: 'variable' })
255
- })
256
-
257
- const [secondMutate, state] = result.current
258
- expect(state).toMatchObject({
259
- loading: false,
260
- called: true,
261
- })
262
- expect(firstMutate).toBe(secondMutate)
263
- })
264
-
265
- it('should resolve with the data from mutate on success', async () => {
266
- const mutation: CreateMutation = {
267
- type: 'create',
268
- resource: 'answer',
269
- data: { answer: '?' },
270
- }
271
- const data = { answer: 42 }
272
- const wrapper = ({ children }: { children?: React.ReactNode }) => (
273
- <CustomDataProvider data={data}>{children}</CustomDataProvider>
274
- )
275
-
276
- const { result } = renderHook(() => useDataMutation(mutation), {
277
- wrapper,
278
- })
279
-
280
- let mutatePromise!: Promise<unknown>
281
- const [mutate] = result.current
282
- act(() => {
283
- mutatePromise = mutate()
284
- })
285
-
286
- await waitFor(() => {
287
- const [, state] = result.current
288
- expect(state).toMatchObject({
289
- loading: false,
290
- called: true,
291
- data: 42,
292
- })
293
- expect(mutatePromise).resolves.toBe(42)
294
- })
295
- })
296
- })
@@ -1,42 +0,0 @@
1
- import type {
2
- QueryOptions,
3
- Mutation,
4
- QueryExecuteOptions,
5
- } from '@dhis2/data-engine'
6
- import { useCallback } from 'react'
7
- import { MutationRenderInput } from '../../types'
8
- import { useDataEngine } from './useDataEngine'
9
- import { useQueryExecutor } from './useQueryExecutor'
10
- import { useStaticInput } from './useStaticInput'
11
-
12
- const empty = {}
13
- export const useDataMutation = (
14
- mutation: Mutation,
15
- { onComplete, onError, variables = empty, lazy = true }: QueryOptions = {}
16
- ): MutationRenderInput => {
17
- const engine = useDataEngine()
18
- const [theMutation] = useStaticInput<Mutation>(mutation, {
19
- warn: true,
20
- name: 'mutation',
21
- })
22
- const execute = useCallback(
23
- (options: QueryExecuteOptions) => engine.mutate(theMutation, options),
24
- [engine, theMutation]
25
- )
26
- const {
27
- refetch: mutate,
28
- called,
29
- loading,
30
- error,
31
- data,
32
- } = useQueryExecutor({
33
- execute,
34
- variables,
35
- singular: false,
36
- immediate: !lazy,
37
- onComplete,
38
- onError,
39
- })
40
-
41
- return [mutate, { engine, called, loading, error, data }]
42
- }