@dfosco/storyboard-react 1.15.0 → 1.15.1
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/package.json +1 -1
- package/src/hooks/useRecord.js +13 -6
- package/src/hooks/useRecord.test.js +50 -1
package/package.json
CHANGED
package/src/hooks/useRecord.js
CHANGED
|
@@ -3,10 +3,15 @@ import { useParams } from 'react-router-dom'
|
|
|
3
3
|
import { loadRecord } from '@dfosco/storyboard-core'
|
|
4
4
|
import { deepClone, setByPath } from '@dfosco/storyboard-core'
|
|
5
5
|
import { getAllParams } from '@dfosco/storyboard-core'
|
|
6
|
+
import { isHideMode, getAllShadows } from '@dfosco/storyboard-core'
|
|
6
7
|
import { subscribeToHash, getHashSnapshot } from '@dfosco/storyboard-core'
|
|
8
|
+
import { subscribeToStorage, getStorageSnapshot } from '@dfosco/storyboard-core'
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
|
-
* Collect
|
|
11
|
+
* Collect overrides for a record and merge them into the base array.
|
|
12
|
+
*
|
|
13
|
+
* In normal mode reads from URL hash params; in hide mode reads from
|
|
14
|
+
* localStorage shadow snapshots.
|
|
10
15
|
*
|
|
11
16
|
* Hash convention: record.{recordName}.{entryId}.{field}=value
|
|
12
17
|
*
|
|
@@ -18,7 +23,7 @@ import { subscribeToHash, getHashSnapshot } from '@dfosco/storyboard-core'
|
|
|
18
23
|
* @returns {Array} Merged array
|
|
19
24
|
*/
|
|
20
25
|
function applyRecordOverrides(baseRecords, recordName) {
|
|
21
|
-
const allParams = getAllParams()
|
|
26
|
+
const allParams = isHideMode() ? getAllShadows() : getAllParams()
|
|
22
27
|
const prefix = `record.${recordName}.`
|
|
23
28
|
|
|
24
29
|
// Collect only the params that target this record
|
|
@@ -87,8 +92,9 @@ export function useRecord(recordName, paramName = 'id') {
|
|
|
87
92
|
const params = useParams()
|
|
88
93
|
const paramValue = params[paramName]
|
|
89
94
|
|
|
90
|
-
// Re-render on hash changes so overrides are reactive
|
|
95
|
+
// Re-render on hash or localStorage changes so overrides are reactive
|
|
91
96
|
const hashString = useSyncExternalStore(subscribeToHash, getHashSnapshot)
|
|
97
|
+
const storageString = useSyncExternalStore(subscribeToStorage, getStorageSnapshot)
|
|
92
98
|
|
|
93
99
|
return useMemo(() => {
|
|
94
100
|
if (!paramValue) return null
|
|
@@ -100,7 +106,7 @@ export function useRecord(recordName, paramName = 'id') {
|
|
|
100
106
|
console.error(`[useRecord] ${err.message}`)
|
|
101
107
|
return null
|
|
102
108
|
}
|
|
103
|
-
}, [recordName, paramName, paramValue, hashString]) // eslint-disable-line react-hooks/exhaustive-deps
|
|
109
|
+
}, [recordName, paramName, paramValue, hashString, storageString]) // eslint-disable-line react-hooks/exhaustive-deps
|
|
104
110
|
}
|
|
105
111
|
|
|
106
112
|
/**
|
|
@@ -115,8 +121,9 @@ export function useRecord(recordName, paramName = 'id') {
|
|
|
115
121
|
* const allPosts = useRecords('posts')
|
|
116
122
|
*/
|
|
117
123
|
export function useRecords(recordName) {
|
|
118
|
-
// Re-render on hash changes so overrides are reactive
|
|
124
|
+
// Re-render on hash or localStorage changes so overrides are reactive
|
|
119
125
|
const hashString = useSyncExternalStore(subscribeToHash, getHashSnapshot)
|
|
126
|
+
const storageString = useSyncExternalStore(subscribeToStorage, getStorageSnapshot)
|
|
120
127
|
|
|
121
128
|
return useMemo(() => {
|
|
122
129
|
try {
|
|
@@ -126,5 +133,5 @@ export function useRecords(recordName) {
|
|
|
126
133
|
console.error(`[useRecords] ${err.message}`)
|
|
127
134
|
return []
|
|
128
135
|
}
|
|
129
|
-
}, [recordName, hashString]) // eslint-disable-line react-hooks/exhaustive-deps
|
|
136
|
+
}, [recordName, hashString, storageString]) // eslint-disable-line react-hooks/exhaustive-deps
|
|
130
137
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { renderHook } from '@testing-library/react'
|
|
1
|
+
import { renderHook, act } from '@testing-library/react'
|
|
2
2
|
import { seedTestData, TEST_RECORDS } from '../../test-utils.js'
|
|
3
|
+
import { activateHideMode, setShadow } from '@dfosco/storyboard-core'
|
|
3
4
|
|
|
4
5
|
vi.mock('react-router-dom', async () => {
|
|
5
6
|
const actual = await vi.importActual('react-router-dom')
|
|
@@ -79,3 +80,51 @@ describe('useRecords', () => {
|
|
|
79
80
|
expect(newPost.title).toBe('New')
|
|
80
81
|
})
|
|
81
82
|
})
|
|
83
|
+
|
|
84
|
+
// ── Hide mode ──
|
|
85
|
+
|
|
86
|
+
describe('useRecord (hide mode)', () => {
|
|
87
|
+
beforeEach(() => {
|
|
88
|
+
act(() => { activateHideMode() })
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('reads overrides from localStorage shadow in hide mode', () => {
|
|
92
|
+
useParams.mockReturnValue({ id: 'post-1' })
|
|
93
|
+
act(() => { setShadow('record.posts.post-1.title', 'Shadow Title') })
|
|
94
|
+
|
|
95
|
+
const { result } = renderHook(() => useRecord('posts'))
|
|
96
|
+
expect(result.current.title).toBe('Shadow Title')
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('reactively updates when shadow changes in hide mode', () => {
|
|
100
|
+
useParams.mockReturnValue({ id: 'post-1' })
|
|
101
|
+
const { result } = renderHook(() => useRecord('posts'))
|
|
102
|
+
expect(result.current.title).toBe('First Post')
|
|
103
|
+
|
|
104
|
+
act(() => { setShadow('record.posts.post-1.title', 'Updated via Shadow') })
|
|
105
|
+
expect(result.current.title).toBe('Updated via Shadow')
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
describe('useRecords (hide mode)', () => {
|
|
110
|
+
beforeEach(() => {
|
|
111
|
+
act(() => { activateHideMode() })
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('applies shadow overrides to existing entries', () => {
|
|
115
|
+
act(() => { setShadow('record.posts.post-1.title', 'Hidden Update') })
|
|
116
|
+
|
|
117
|
+
const { result } = renderHook(() => useRecords('posts'))
|
|
118
|
+
const post1 = result.current.find(e => e.id === 'post-1')
|
|
119
|
+
expect(post1.title).toBe('Hidden Update')
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('creates new entries from shadow overrides', () => {
|
|
123
|
+
act(() => { setShadow('record.posts.shadow-post.title', 'New Shadow') })
|
|
124
|
+
|
|
125
|
+
const { result } = renderHook(() => useRecords('posts'))
|
|
126
|
+
const newPost = result.current.find(e => e.id === 'shadow-post')
|
|
127
|
+
expect(newPost).toBeTruthy()
|
|
128
|
+
expect(newPost.title).toBe('New Shadow')
|
|
129
|
+
})
|
|
130
|
+
})
|