Skip to content

Commit

Permalink
added epoch to proxySet and proxyMap to keep track of versioning
Browse files Browse the repository at this point in the history
  • Loading branch information
overthemike committed Oct 24, 2024
1 parent 660866e commit f1a4d82
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 6 deletions.
11 changes: 9 additions & 2 deletions src/vanilla/utils/proxyMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const isProxy = (x: any) => proxyStateMap.has(x)
type InternalProxyObject<K, V> = Map<K, V> & {
data: Array<V>
index: number
epoch: number
toJSON: () => Map<K, V>
}

Expand Down Expand Up @@ -68,6 +69,7 @@ export function proxyMap<K, V>(entries?: Iterable<[K, V]> | undefined | null) {
const vObject: InternalProxyObject<K, V> = {
data: initialData,
index: initialIndex,
epoch: 0,
get size() {
if (!isProxy(this)) {
registerSnapMap()
Expand All @@ -80,15 +82,16 @@ export function proxyMap<K, V>(entries?: Iterable<[K, V]> | undefined | null) {
const index = map.get(key)
if (index === undefined) {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
this.index // touch property for tracking
this.epoch // touch property for tracking
return undefined
}
return this.data[index]
},
has(key: K) {
const map = getMapForThis(this)
const exists = map.has(key)

Check warning on line 92 in src/vanilla/utils/proxyMap.ts

View workflow job for this annotation

GitHub Actions / lint

'exists' is assigned a value but never used. Allowed unused vars must match /^_/u
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
this.data.length // touch property for tracking
this.epoch
return map.has(key)
},
set(key: K, value: V) {
Expand All @@ -102,6 +105,7 @@ export function proxyMap<K, V>(entries?: Iterable<[K, V]> | undefined | null) {
} else {
this.data[index] = value
}
this.epoch++
return this
},
delete(key: K) {
Expand All @@ -114,6 +118,7 @@ export function proxyMap<K, V>(entries?: Iterable<[K, V]> | undefined | null) {
}
delete this.data[index]
indexMap.delete(key)
this.epoch++
return true
},
clear() {
Expand All @@ -122,6 +127,7 @@ export function proxyMap<K, V>(entries?: Iterable<[K, V]> | undefined | null) {
}
this.data.length = 0 // empty array
this.index = 0
this.epoch++
indexMap.clear()
},
forEach(cb: (value: V, key: K, map: Map<K, V>) => void) {
Expand Down Expand Up @@ -163,6 +169,7 @@ export function proxyMap<K, V>(entries?: Iterable<[K, V]> | undefined | null) {
Object.defineProperties(proxiedObject, {
size: { enumerable: false },
index: { enumerable: false },
epoch: { enumerable: false },
data: { enumerable: false },
toJSON: { enumerable: false },
})
Expand Down
9 changes: 8 additions & 1 deletion src/vanilla/utils/proxySet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type InternalProxySet<T> = Set<T> & {
data: T[]
toJSON: object
index: number
epoch: number
intersection: (other: Set<T>) => Set<T>
isDisjointFrom: (other: Set<T>) => boolean
isSubsetOf: (other: Set<T>) => boolean
Expand Down Expand Up @@ -63,6 +64,7 @@ export function proxySet<T>(initialValues?: Iterable<T> | null) {
const vObject: InternalProxySet<T> = {
data: initialData,
index: initialIndex,
epoch: 0,
get size() {
if (!isProxy(this)) {
registerSnapMap()
Expand All @@ -73,7 +75,7 @@ export function proxySet<T>(initialValues?: Iterable<T> | null) {
const map = getMapForThis(this)
const v = maybeProxify(value)
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
this.data.length // touch property for tracking
this.epoch // touch property for tracking
return map.has(v)
},
add(value: T) {
Expand All @@ -84,6 +86,7 @@ export function proxySet<T>(initialValues?: Iterable<T> | null) {
if (!indexMap.has(v)) {
indexMap.set(v, this.index)
this.data[this.index++] = v
this.epoch++
}
return this
},
Expand All @@ -98,6 +101,7 @@ export function proxySet<T>(initialValues?: Iterable<T> | null) {
}
delete this.data[index]
indexMap.delete(v)
this.epoch++
return true
},
clear() {
Expand All @@ -106,6 +110,7 @@ export function proxySet<T>(initialValues?: Iterable<T> | null) {
}
this.data.length = 0 // empty array
this.index = 0
this.epoch++
indexMap.clear()
},
forEach(cb) {
Expand Down Expand Up @@ -197,6 +202,8 @@ export function proxySet<T>(initialValues?: Iterable<T> | null) {
Object.defineProperties(proxiedObject, {
size: { enumerable: false },
data: { enumerable: false },
index: { enumerable: false },
epoch: { enumerable: false },
toJSON: { enumerable: false },
})
Object.seal(proxiedObject)
Expand Down
55 changes: 54 additions & 1 deletion tests/proxyMap.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ describe('snapshot', () => {
})

describe('ui updates - useSnapshot', async () => {
it('should update ui when calling has before and after deleting a key', async () => {
it('should update ui when calling has before and after setting and deleting a key', async () => {
const state = proxyMap()
const TestComponent = () => {
const snap = useSnapshot(state)
Expand Down Expand Up @@ -412,4 +412,57 @@ describe('ui updates - useSnapshot', async () => {
getByText('has key: false')
})
})

it('should update ui when calling has before and after settiing and deleting multiple keys', async () => {
const state = proxyMap()
const TestComponent = () => {
const snap = useSnapshot(state)

return (
<>
<p>has key: {`${snap.has('key')}`}</p>
<p>has key2: {`${snap.has('key2')}`}</p>
<button
onClick={() => {
state.set('key', 'value')
state.set('key2', 'value')
}}
>
set keys
</button>
<button
onClick={() => {
state.delete('key')
state.delete('key2')
}}
>
delete keys
</button>
</>
)
}

const { getByText } = render(
<StrictMode>
<TestComponent />
</StrictMode>,
)

await waitFor(() => {
getByText('has key: false')
getByText('has key2: false')
})

fireEvent.click(getByText('set keys'))
await waitFor(() => {
getByText('has key: true')
getByText('has key2: true')
})

fireEvent.click(getByText('delete keys'))
await waitFor(() => {
getByText('has key: false')
getByText('has key2: false')
})
})
})
64 changes: 62 additions & 2 deletions tests/proxySet.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -367,15 +367,22 @@ describe('snapshot behavior', () => {
})

describe('ui updates - useSnapshot', async () => {
it('should update ui when calling has before and after deleting a value', async () => {
it('should update ui when calling has before and after setting anddeleting a value', async () => {
const state = proxySet()
const TestComponent = () => {
const snap = useSnapshot(state)

return (
<>
<p>has value: {`${snap.has('value')}`}</p>
<button onClick={() => state.add('value')}>add value</button>
<button
onClick={() => {
state.add('value')
state.add('value2')
}}
>
add value
</button>
<button onClick={() => state.delete('value')}>delete value</button>
</>
)
Expand All @@ -401,4 +408,57 @@ describe('ui updates - useSnapshot', async () => {
getByText('has value: false')
})
})

it('should update ui when calling has before and after settiing and deleting multiple values', async () => {
const state = proxySet()
const TestComponent = () => {
const snap = useSnapshot(state)

return (
<>
<p>has value: {`${snap.has('value')}`}</p>
<p>has value2: {`${snap.has('value2')}`}</p>
<button
onClick={() => {
state.add('value')
state.add('value2')
}}
>
add values
</button>
<button
onClick={() => {
state.delete('value')
state.delete('value2')
}}
>
delete values
</button>
</>
)
}

const { getByText } = render(
<StrictMode>
<TestComponent />
</StrictMode>,
)

await waitFor(() => {
getByText('has value: false')
getByText('has value2: false')
})

fireEvent.click(getByText('add values'))
await waitFor(() => {
getByText('has value: true')
getByText('has value2: true')
})

fireEvent.click(getByText('delete values'))
await waitFor(() => {
getByText('has value: false')
getByText('has value2: false')
})
})
})

0 comments on commit f1a4d82

Please sign in to comment.