fix: deep-freeze DEFAULT_STATE nested arrays to prevent shared mutation

Amp-Thread-ID: https://ampcode.com/threads/T-019cbd94-a928-7610-b468-2583f4816262
This commit is contained in:
bymyself
2026-03-05 02:41:23 -08:00
parent 6da7c896c9
commit 7cd10ccd88
2 changed files with 25 additions and 2 deletions

View File

@@ -305,6 +305,25 @@ describe(useNodeImageStore, () => {
})
})
describe('DEFAULT_STATE immutability', () => {
it('default imageRects is frozen and cannot be mutated', () => {
const node = createMockNode()
mockNodeToNodeLocatorId.mockReturnValue(locatorA)
store.installPropertyProjection(node)
// Read default imageRects without triggering state creation
const nodeB = createMockNode()
mockNodeToNodeLocatorId.mockReturnValue(locatorB)
store.installPropertyProjection(nodeB)
// Default arrays should be frozen (no state entry exists yet)
expect(() => {
;(nodeB.imageRects as unknown[]).push([0, 0, 10, 10])
}).toThrow()
})
})
describe('null-to-null transitions', () => {
it('imageIndex null → null works', () => {
const node = createMockNode()

View File

@@ -17,8 +17,6 @@ interface NodeImageState {
overIndex: number | null
}
const DEFAULT_STATE = Object.freeze(createDefaultState())
function createDefaultState(): NodeImageState {
return {
imgs: [],
@@ -29,6 +27,12 @@ function createDefaultState(): NodeImageState {
}
}
const DEFAULT_STATE: Readonly<NodeImageState> = Object.freeze({
...createDefaultState(),
imgs: Object.freeze([]) as unknown as HTMLImageElement[],
imageRects: Object.freeze([]) as unknown as Rect[]
})
/**
* Module-scoped resolver for converting nodes to locator IDs.
* Set once during app bootstrap via {@link setNodeLocatorResolver} to