@rpg-engine/long-bow 0.8.219 → 0.8.221
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/dist/components/DraggableContainer.d.ts +0 -6
- package/dist/components/Store/CartView.d.ts +0 -2
- package/dist/components/Store/MetadataCollector.d.ts +2 -2
- package/dist/components/Store/Store.d.ts +10 -28
- package/dist/components/Store/StoreHeader.d.ts +14 -0
- package/dist/components/Store/hooks/useStoreCart.d.ts +2 -0
- package/dist/components/Store/hooks/useStoreMetadata.d.ts +4 -11
- package/dist/components/Store/hooks/useStoreTabs.d.ts +20 -0
- package/dist/components/Store/internal/packToBlueprint.d.ts +2 -0
- package/dist/components/Store/sections/StoreItemsSection.d.ts +5 -3
- package/dist/hooks/useStoreFiltering.d.ts +7 -4
- package/dist/long-bow.cjs.development.js +379 -441
- package/dist/long-bow.cjs.development.js.map +1 -1
- package/dist/long-bow.cjs.production.min.js +1 -1
- package/dist/long-bow.cjs.production.min.js.map +1 -1
- package/dist/long-bow.esm.js +381 -443
- package/dist/long-bow.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/components/DraggableContainer.tsx +0 -24
- package/src/components/Store/CartView.tsx +116 -137
- package/src/components/Store/MetadataCollector.tsx +60 -40
- package/src/components/Store/Store.tsx +75 -285
- package/src/components/Store/StoreHeader.tsx +74 -0
- package/src/components/Store/__test__/MetadataCollector.spec.tsx +94 -164
- package/src/components/Store/__test__/Store.spec.tsx +4 -0
- package/src/components/Store/__test__/useStoreMetadata.spec.tsx +58 -156
- package/src/components/Store/__test__/useStoreTabs.spec.tsx +69 -0
- package/src/components/Store/hooks/useStoreCart.ts +5 -2
- package/src/components/Store/hooks/useStoreMetadata.ts +30 -48
- package/src/components/Store/hooks/useStoreTabs.ts +104 -0
- package/src/components/Store/internal/packToBlueprint.ts +21 -0
- package/src/components/Store/sections/StoreItemsSection.tsx +19 -60
- package/src/components/Store/sections/StorePacksSection.tsx +0 -1
- package/src/components/shared/ScrollableContent/ScrollableContent.tsx +3 -6
- package/src/hooks/useStoreFiltering.spec.tsx +79 -0
- package/src/hooks/useStoreFiltering.ts +27 -9
|
@@ -1,95 +1,47 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
// @ts-nocheck
|
|
2
5
|
import { MetadataType } from '@rpg-engine/shared';
|
|
3
6
|
import React from 'react';
|
|
7
|
+
import ReactDOM from 'react-dom';
|
|
8
|
+
import { act } from 'react-dom/test-utils';
|
|
4
9
|
import { CharacterSkinSelectionModal } from '../../Character/CharacterSkinSelectionModal';
|
|
5
10
|
import { MetadataCollector } from '../MetadataCollector';
|
|
6
11
|
|
|
7
|
-
// If MetadataType is not properly loaded, mock it with the same values
|
|
8
12
|
jest.mock('@rpg-engine/shared', () => ({
|
|
9
|
-
// Keep any real values from the shared module
|
|
10
13
|
...jest.requireActual('@rpg-engine/shared'),
|
|
11
|
-
// Override MetadataType with our mock values
|
|
12
14
|
MetadataType: {
|
|
13
15
|
None: 'None',
|
|
14
|
-
CharacterSkin: 'CharacterSkin'
|
|
15
|
-
}
|
|
16
|
+
CharacterSkin: 'CharacterSkin',
|
|
17
|
+
},
|
|
16
18
|
}));
|
|
17
19
|
|
|
18
|
-
// Mock the CharacterSkinSelectionModal component
|
|
19
20
|
jest.mock('../../Character/CharacterSkinSelectionModal', () => ({
|
|
20
21
|
CharacterSkinSelectionModal: jest.fn(() => <div>Skin Selection Modal</div>),
|
|
21
22
|
}));
|
|
22
23
|
|
|
23
|
-
// We need to mock the actual MetadataCollector component
|
|
24
|
-
jest.mock('../MetadataCollector', () => ({
|
|
25
|
-
MetadataCollector: jest.fn(({ metadataType, config, onCollect, onCancel }) => {
|
|
26
|
-
// Set up cleanup function for the useEffect tests
|
|
27
|
-
const React = require('react');
|
|
28
|
-
React.useEffect(() => {
|
|
29
|
-
return () => {
|
|
30
|
-
if (globalThis.__metadataResolvers) {
|
|
31
|
-
onCancel();
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
}, [onCancel]);
|
|
35
|
-
|
|
36
|
-
// This mock simulates the switch statement in the original component
|
|
37
|
-
if (metadataType === 'CharacterSkin') {
|
|
38
|
-
// Call the CharacterSkinSelectionModal
|
|
39
|
-
const CharacterSkinMock = require('../../Character/CharacterSkinSelectionModal').CharacterSkinSelectionModal;
|
|
40
|
-
CharacterSkinMock({
|
|
41
|
-
isOpen: true,
|
|
42
|
-
onClose: onCancel,
|
|
43
|
-
onConfirm: (selectedSkin) => onCollect({ selectedSkin }),
|
|
44
|
-
availableCharacters: config.availableCharacters || [],
|
|
45
|
-
atlasJSON: config.atlasJSON,
|
|
46
|
-
atlasIMG: config.atlasIMG,
|
|
47
|
-
initialSelectedSkin: config.initialSelectedSkin
|
|
48
|
-
});
|
|
49
|
-
return null;
|
|
50
|
-
} else {
|
|
51
|
-
// Warn for unhandled types and auto-cancel
|
|
52
|
-
console.warn(`No collector implemented for metadata type: ${metadataType}`);
|
|
53
|
-
setTimeout(onCancel, 0);
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
})
|
|
57
|
-
}));
|
|
58
|
-
|
|
59
|
-
// Mock globals and setup
|
|
60
|
-
let cleanupFunction;
|
|
61
|
-
let mockOnCollect;
|
|
62
|
-
let mockOnCancel;
|
|
63
|
-
|
|
64
|
-
// Mock window.__metadataResolvers
|
|
65
|
-
Object.defineProperty(window, '__metadataResolvers', {
|
|
66
|
-
writable: true,
|
|
67
|
-
value: undefined
|
|
68
|
-
});
|
|
69
|
-
|
|
70
24
|
describe('MetadataCollector', () => {
|
|
25
|
+
let container;
|
|
26
|
+
let mockOnCollect;
|
|
27
|
+
let mockOnCancel;
|
|
28
|
+
|
|
71
29
|
beforeEach(() => {
|
|
72
|
-
// Reset mocks
|
|
73
30
|
jest.clearAllMocks();
|
|
74
31
|
mockOnCollect = jest.fn();
|
|
75
32
|
mockOnCancel = jest.fn();
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
window.__metadataResolvers = undefined;
|
|
80
|
-
|
|
81
|
-
// Mock React's useEffect to capture cleanup function
|
|
82
|
-
jest.spyOn(React, 'useEffect').mockImplementation(cb => {
|
|
83
|
-
cleanupFunction = cb();
|
|
84
|
-
});
|
|
33
|
+
jest.useFakeTimers();
|
|
34
|
+
container = document.createElement('div');
|
|
35
|
+
document.body.appendChild(container);
|
|
85
36
|
});
|
|
86
37
|
|
|
87
38
|
afterEach(() => {
|
|
88
|
-
|
|
89
|
-
|
|
39
|
+
ReactDOM.unmountComponentAtNode(container);
|
|
40
|
+
document.body.removeChild(container);
|
|
41
|
+
jest.useRealTimers();
|
|
90
42
|
});
|
|
91
43
|
|
|
92
|
-
it('
|
|
44
|
+
it('renders CharacterSkinSelectionModal for CharacterSkin metadata type', () => {
|
|
93
45
|
const mockConfig = {
|
|
94
46
|
availableCharacters: ['char1', 'char2'],
|
|
95
47
|
atlasJSON: { frames: {} },
|
|
@@ -97,133 +49,111 @@ describe('MetadataCollector', () => {
|
|
|
97
49
|
initialSelectedSkin: 'char1',
|
|
98
50
|
};
|
|
99
51
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
52
|
+
act(() => {
|
|
53
|
+
ReactDOM.render(
|
|
54
|
+
<MetadataCollector
|
|
55
|
+
metadataType={MetadataType.CharacterSkin}
|
|
56
|
+
config={mockConfig}
|
|
57
|
+
onCollect={mockOnCollect}
|
|
58
|
+
onCancel={mockOnCancel}
|
|
59
|
+
/>,
|
|
60
|
+
container
|
|
61
|
+
);
|
|
106
62
|
});
|
|
107
63
|
|
|
108
|
-
// Verify CharacterSkinSelectionModal was called
|
|
109
64
|
expect(CharacterSkinSelectionModal).toHaveBeenCalled();
|
|
110
|
-
|
|
111
|
-
// Get the first call arguments
|
|
112
65
|
const callArgs = CharacterSkinSelectionModal.mock.calls[0][0];
|
|
113
|
-
|
|
114
|
-
// Verify individual properties
|
|
66
|
+
|
|
115
67
|
expect(callArgs.isOpen).toBe(true);
|
|
116
|
-
expect(callArgs.onClose).toBe(mockOnCancel);
|
|
117
68
|
expect(callArgs.availableCharacters).toEqual(mockConfig.availableCharacters);
|
|
118
69
|
expect(callArgs.atlasJSON).toEqual(mockConfig.atlasJSON);
|
|
119
70
|
expect(callArgs.atlasIMG).toEqual(mockConfig.atlasIMG);
|
|
120
71
|
expect(callArgs.initialSelectedSkin).toEqual(mockConfig.initialSelectedSkin);
|
|
121
72
|
});
|
|
122
73
|
|
|
123
|
-
it('
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
return <div>Skin Selection Modal</div>;
|
|
74
|
+
it('collects once without cancelling again when the modal closes after confirm', () => {
|
|
75
|
+
act(() => {
|
|
76
|
+
ReactDOM.render(
|
|
77
|
+
<MetadataCollector
|
|
78
|
+
metadataType={MetadataType.CharacterSkin}
|
|
79
|
+
config={{ availableCharacters: ['char1', 'char2'] }}
|
|
80
|
+
onCollect={mockOnCollect}
|
|
81
|
+
onCancel={mockOnCancel}
|
|
82
|
+
/>,
|
|
83
|
+
container
|
|
84
|
+
);
|
|
135
85
|
});
|
|
136
86
|
|
|
137
|
-
|
|
138
|
-
MetadataCollector({
|
|
139
|
-
metadataType: MetadataType.CharacterSkin,
|
|
140
|
-
config: mockConfig,
|
|
141
|
-
onCollect: mockOnCollect,
|
|
142
|
-
onCancel: mockOnCancel,
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
// Verify onCollect was called with the right parameters
|
|
146
|
-
expect(mockOnCollect).toHaveBeenCalledWith({ selectedSkin: 'char1' });
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
it('should log warning and auto-cancel for unhandled metadata types', () => {
|
|
150
|
-
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
151
|
-
|
|
152
|
-
// Mock setTimeout to execute callback immediately
|
|
153
|
-
jest.spyOn(global, 'setTimeout').mockImplementation(callback => {
|
|
154
|
-
callback();
|
|
155
|
-
return 0;
|
|
156
|
-
});
|
|
87
|
+
const callArgs = CharacterSkinSelectionModal.mock.calls[0][0];
|
|
157
88
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
config: {},
|
|
162
|
-
onCollect: mockOnCollect,
|
|
163
|
-
onCancel: mockOnCancel,
|
|
89
|
+
act(() => {
|
|
90
|
+
callArgs.onConfirm('char1');
|
|
91
|
+
callArgs.onClose();
|
|
164
92
|
});
|
|
165
93
|
|
|
166
|
-
|
|
167
|
-
expect(
|
|
168
|
-
|
|
169
|
-
);
|
|
94
|
+
expect(mockOnCollect).toHaveBeenCalledWith({ selectedSkin: 'char1' });
|
|
95
|
+
expect(mockOnCollect).toHaveBeenCalledTimes(1);
|
|
96
|
+
expect(mockOnCancel).not.toHaveBeenCalled();
|
|
170
97
|
|
|
171
|
-
|
|
172
|
-
|
|
98
|
+
act(() => {
|
|
99
|
+
ReactDOM.unmountComponentAtNode(container);
|
|
100
|
+
});
|
|
173
101
|
|
|
174
|
-
|
|
175
|
-
warnSpy.mockRestore();
|
|
176
|
-
jest.spyOn(global, 'setTimeout').mockRestore();
|
|
102
|
+
expect(mockOnCancel).not.toHaveBeenCalled();
|
|
177
103
|
});
|
|
178
104
|
|
|
179
|
-
it('
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
105
|
+
it('cancels once when closed and does not cancel again on unmount', () => {
|
|
106
|
+
act(() => {
|
|
107
|
+
ReactDOM.render(
|
|
108
|
+
<MetadataCollector
|
|
109
|
+
metadataType={MetadataType.CharacterSkin}
|
|
110
|
+
config={{ availableCharacters: ['char1', 'char2'] }}
|
|
111
|
+
onCollect={mockOnCollect}
|
|
112
|
+
onCancel={mockOnCancel}
|
|
113
|
+
/>,
|
|
114
|
+
container
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const callArgs = CharacterSkinSelectionModal.mock.calls[0][0];
|
|
185
119
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
metadataType: MetadataType.CharacterSkin,
|
|
189
|
-
config: {},
|
|
190
|
-
onCollect: mockOnCollect,
|
|
191
|
-
onCancel: mockOnCancel,
|
|
120
|
+
act(() => {
|
|
121
|
+
callArgs.onClose();
|
|
192
122
|
});
|
|
193
123
|
|
|
194
|
-
|
|
195
|
-
mockOnCancel.mockReset();
|
|
124
|
+
expect(mockOnCancel).toHaveBeenCalledTimes(1);
|
|
196
125
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
126
|
+
act(() => {
|
|
127
|
+
ReactDOM.unmountComponentAtNode(container);
|
|
128
|
+
});
|
|
201
129
|
|
|
202
|
-
|
|
203
|
-
expect(mockOnCancel).toHaveBeenCalled();
|
|
130
|
+
expect(mockOnCancel).toHaveBeenCalledTimes(1);
|
|
204
131
|
});
|
|
205
132
|
|
|
206
|
-
it('
|
|
207
|
-
|
|
208
|
-
window.__metadataResolvers = undefined;
|
|
133
|
+
it('logs a warning and auto-cancels for unhandled metadata types', () => {
|
|
134
|
+
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
209
135
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
136
|
+
act(() => {
|
|
137
|
+
ReactDOM.render(
|
|
138
|
+
<MetadataCollector
|
|
139
|
+
metadataType={'UnknownType' as MetadataType}
|
|
140
|
+
config={{}}
|
|
141
|
+
onCollect={mockOnCollect}
|
|
142
|
+
onCancel={mockOnCancel}
|
|
143
|
+
/>,
|
|
144
|
+
container
|
|
145
|
+
);
|
|
216
146
|
});
|
|
217
147
|
|
|
218
|
-
|
|
219
|
-
|
|
148
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
149
|
+
expect.stringContaining('No collector implemented for metadata type: UnknownType')
|
|
150
|
+
);
|
|
220
151
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
152
|
+
act(() => {
|
|
153
|
+
jest.runAllTimers();
|
|
154
|
+
});
|
|
225
155
|
|
|
226
|
-
|
|
227
|
-
|
|
156
|
+
expect(mockOnCancel).toHaveBeenCalledTimes(1);
|
|
157
|
+
warnSpy.mockRestore();
|
|
228
158
|
});
|
|
229
|
-
});
|
|
159
|
+
});
|
|
@@ -105,6 +105,9 @@ jest.mock('../hooks/useStoreCart', () => ({
|
|
|
105
105
|
getTotalItems: () => 0,
|
|
106
106
|
getTotalPrice: () => 0,
|
|
107
107
|
isCartOpen: false,
|
|
108
|
+
isCollectingMetadata: false,
|
|
109
|
+
currentMetadataItem: null,
|
|
110
|
+
resolveMetadata: jest.fn(),
|
|
108
111
|
}),
|
|
109
112
|
}));
|
|
110
113
|
|
|
@@ -128,6 +131,7 @@ jest.mock('react-icons/fa', () => ({
|
|
|
128
131
|
FaHistory: () => <span>history</span>,
|
|
129
132
|
FaShoppingCart: () => <span>cart</span>,
|
|
130
133
|
FaTicketAlt: () => <span>ticket</span>,
|
|
134
|
+
FaUsers: () => <span>users</span>,
|
|
131
135
|
FaWallet: () => <span>wallet</span>,
|
|
132
136
|
}));
|
|
133
137
|
|
|
@@ -1,181 +1,83 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
// @ts-nocheck
|
|
2
5
|
import { MetadataType } from '@rpg-engine/shared';
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import ReactDOM from 'react-dom';
|
|
8
|
+
import { act } from 'react-dom/test-utils';
|
|
3
9
|
import { useStoreMetadata } from '../hooks/useStoreMetadata';
|
|
4
10
|
|
|
5
|
-
// If MetadataType is not properly loaded, mock it with the same values
|
|
6
11
|
jest.mock('@rpg-engine/shared', () => ({
|
|
7
|
-
// Keep any real values from the shared module
|
|
8
12
|
...jest.requireActual('@rpg-engine/shared'),
|
|
9
|
-
|
|
10
|
-
MetadataType: {
|
|
11
|
-
None: 'None',
|
|
12
|
-
CharacterSkin: 'CharacterSkin'
|
|
13
|
-
}
|
|
13
|
+
MetadataType: { None: 'None', CharacterSkin: 'CharacterSkin' },
|
|
14
14
|
}));
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const useSM = jest.fn().mockImplementation(() => {
|
|
19
|
-
const isCollectingMetadata = false;
|
|
20
|
-
|
|
21
|
-
const collectMetadata = async (item) => {
|
|
22
|
-
// If no metadata type or None, return null immediately
|
|
23
|
-
if (!item.metadataType || item.metadataType === 'None') {
|
|
24
|
-
return null;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Setup for valid metadata types
|
|
28
|
-
globalThis.__metadataResolvers = {
|
|
29
|
-
resolve: (metadata) => {
|
|
30
|
-
// Clean up
|
|
31
|
-
globalThis.__metadataResolvers = undefined;
|
|
32
|
-
return metadata;
|
|
33
|
-
},
|
|
34
|
-
item
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
// Handle the last test case specifically
|
|
38
|
-
if (item.key === 'item-character-skin-metadata-cancel') {
|
|
39
|
-
globalThis.__metadataResolvers = undefined; // Make sure it's cleaned up
|
|
40
|
-
return Promise.resolve(null);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Normal flow
|
|
44
|
-
return Promise.resolve({ selectedSkin: 'test-skin' });
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
return {
|
|
48
|
-
isCollectingMetadata,
|
|
49
|
-
collectMetadata
|
|
50
|
-
};
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
return { useStoreMetadata: useSM };
|
|
54
|
-
});
|
|
16
|
+
const itemNoMetadata = { key: 'plain', name: 'Plain', price: 100 };
|
|
17
|
+
const itemCharSkin = { key: 'skin', name: 'Skin', price: 300, metadataType: 'CharacterSkin' };
|
|
55
18
|
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
19
|
+
// Render the hook in a real React component to satisfy Hook rules
|
|
20
|
+
let hookResult;
|
|
21
|
+
const TestComponent = () => {
|
|
22
|
+
hookResult = useStoreMetadata();
|
|
23
|
+
return null;
|
|
24
|
+
};
|
|
61
25
|
|
|
62
26
|
describe('useStoreMetadata', () => {
|
|
63
|
-
|
|
64
|
-
const mockItemNoMetadata = {
|
|
65
|
-
key: 'item-no-metadata',
|
|
66
|
-
name: 'Test Item 1',
|
|
67
|
-
price: 100,
|
|
68
|
-
textureAtlas: 'atlas',
|
|
69
|
-
textureKey: 'texture',
|
|
70
|
-
rarity: 'common',
|
|
71
|
-
allowedEquipSlotType: 'hand'
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
const mockItemWithNoneMetadata = {
|
|
75
|
-
key: 'item-none-metadata',
|
|
76
|
-
name: 'Test Item 2',
|
|
77
|
-
price: 200,
|
|
78
|
-
metadataType: MetadataType.None,
|
|
79
|
-
textureAtlas: 'atlas',
|
|
80
|
-
textureKey: 'texture',
|
|
81
|
-
rarity: 'common',
|
|
82
|
-
allowedEquipSlotType: 'hand'
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
const mockItemWithCharacterSkinMetadata = {
|
|
86
|
-
key: 'item-character-skin-metadata',
|
|
87
|
-
name: 'Test Item 3',
|
|
88
|
-
price: 300,
|
|
89
|
-
metadataType: MetadataType.CharacterSkin,
|
|
90
|
-
textureAtlas: 'atlas',
|
|
91
|
-
textureKey: 'texture',
|
|
92
|
-
rarity: 'common',
|
|
93
|
-
allowedEquipSlotType: 'hand'
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
const mockItemWithCharacterSkinMetadataCancel = {
|
|
97
|
-
key: 'item-character-skin-metadata-cancel',
|
|
98
|
-
name: 'Test Item 4',
|
|
99
|
-
price: 300,
|
|
100
|
-
metadataType: MetadataType.CharacterSkin,
|
|
101
|
-
textureAtlas: 'atlas',
|
|
102
|
-
textureKey: 'texture',
|
|
103
|
-
rarity: 'common',
|
|
104
|
-
allowedEquipSlotType: 'hand'
|
|
105
|
-
};
|
|
27
|
+
let container;
|
|
106
28
|
|
|
107
29
|
beforeEach(() => {
|
|
108
|
-
|
|
109
|
-
|
|
30
|
+
container = document.createElement('div');
|
|
31
|
+
document.body.appendChild(container);
|
|
32
|
+
act(() => { ReactDOM.render(<TestComponent />, container); });
|
|
33
|
+
});
|
|
110
34
|
|
|
111
|
-
|
|
112
|
-
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
ReactDOM.unmountComponentAtNode(container);
|
|
37
|
+
document.body.removeChild(container);
|
|
113
38
|
});
|
|
114
39
|
|
|
115
|
-
it('
|
|
116
|
-
|
|
117
|
-
expect(
|
|
40
|
+
it('initializes with isCollectingMetadata false and no currentMetadataItem', () => {
|
|
41
|
+
expect(hookResult.isCollectingMetadata).toBe(false);
|
|
42
|
+
expect(hookResult.currentMetadataItem).toBeNull();
|
|
118
43
|
});
|
|
119
44
|
|
|
120
|
-
it('
|
|
121
|
-
const
|
|
122
|
-
const result = await hook.collectMetadata(mockItemNoMetadata);
|
|
45
|
+
it('returns null immediately for items without metadataType', async () => {
|
|
46
|
+
const result = await hookResult.collectMetadata(itemNoMetadata);
|
|
123
47
|
expect(result).toBeNull();
|
|
48
|
+
expect(hookResult.isCollectingMetadata).toBe(false);
|
|
124
49
|
});
|
|
125
50
|
|
|
126
|
-
it('
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
expect(
|
|
51
|
+
it('sets isCollectingMetadata and currentMetadataItem when collecting starts', () => {
|
|
52
|
+
act(() => { hookResult.collectMetadata(itemCharSkin); });
|
|
53
|
+
expect(hookResult.isCollectingMetadata).toBe(true);
|
|
54
|
+
expect(hookResult.currentMetadataItem).toBe(itemCharSkin);
|
|
55
|
+
// clean up
|
|
56
|
+
act(() => { hookResult.resolveMetadata(null); });
|
|
130
57
|
});
|
|
131
58
|
|
|
132
|
-
it('
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const originalResolve = globalThis.__metadataResolvers.resolve;
|
|
144
|
-
globalThis.__metadataResolvers.resolve = function(metadata) {
|
|
145
|
-
resolveSpy(metadata);
|
|
146
|
-
return originalResolve(metadata);
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Resolve the metadata
|
|
151
|
-
const mockMetadata = { selectedSkin: 'test-skin' };
|
|
152
|
-
if (globalThis.__metadataResolvers) {
|
|
153
|
-
globalThis.__metadataResolvers.resolve(mockMetadata);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Wait for the promise to resolve
|
|
157
|
-
const result = await metadataPromise;
|
|
158
|
-
|
|
159
|
-
// Assertions
|
|
160
|
-
expect(result).toEqual(mockMetadata);
|
|
161
|
-
expect(resolveSpy).toHaveBeenCalledWith(mockMetadata);
|
|
162
|
-
expect(globalThis.__metadataResolvers).toBeUndefined(); // Should be cleaned up
|
|
59
|
+
it('resolves with metadata and resets state on resolveMetadata', async () => {
|
|
60
|
+
let promise;
|
|
61
|
+
act(() => { promise = hookResult.collectMetadata(itemCharSkin); });
|
|
62
|
+
|
|
63
|
+
const mockMetadata = { selectedSkin: 'warrior' };
|
|
64
|
+
act(() => { hookResult.resolveMetadata(mockMetadata); });
|
|
65
|
+
|
|
66
|
+
const resolved = await promise;
|
|
67
|
+
expect(resolved).toEqual(mockMetadata);
|
|
68
|
+
expect(hookResult.isCollectingMetadata).toBe(false);
|
|
69
|
+
expect(hookResult.currentMetadataItem).toBeNull();
|
|
163
70
|
});
|
|
164
71
|
|
|
165
|
-
it('
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const result = await metadataPromise;
|
|
176
|
-
|
|
177
|
-
// Assertions
|
|
178
|
-
expect(result).toBeNull();
|
|
179
|
-
expect(window.__metadataResolvers).toBeUndefined(); // Should be cleaned up
|
|
72
|
+
it('resolves with null and resets state when cancelled', async () => {
|
|
73
|
+
let promise;
|
|
74
|
+
act(() => { promise = hookResult.collectMetadata(itemCharSkin); });
|
|
75
|
+
|
|
76
|
+
act(() => { hookResult.resolveMetadata(null); });
|
|
77
|
+
|
|
78
|
+
const resolved = await promise;
|
|
79
|
+
expect(resolved).toBeNull();
|
|
80
|
+
expect(hookResult.isCollectingMetadata).toBe(false);
|
|
81
|
+
expect(hookResult.currentMetadataItem).toBeNull();
|
|
180
82
|
});
|
|
181
|
-
});
|
|
83
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
// @ts-nocheck
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import ReactDOM from 'react-dom';
|
|
7
|
+
import { act } from 'react-dom/test-utils';
|
|
8
|
+
import { useStoreTabs } from '../hooks/useStoreTabs';
|
|
9
|
+
|
|
10
|
+
let hookResult;
|
|
11
|
+
|
|
12
|
+
const TestComponent = (props) => {
|
|
13
|
+
hookResult = useStoreTabs(props);
|
|
14
|
+
return null;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
describe('useStoreTabs', () => {
|
|
18
|
+
let container;
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
container = document.createElement('div');
|
|
22
|
+
document.body.appendChild(container);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
ReactDOM.unmountComponentAtNode(container);
|
|
27
|
+
document.body.removeChild(container);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('falls back to the next valid tab when the active tab disappears', () => {
|
|
31
|
+
act(() => {
|
|
32
|
+
ReactDOM.render(
|
|
33
|
+
<TestComponent tabOrder={['wallet', 'items']} hasWallet defaultActiveTab="wallet" />,
|
|
34
|
+
container
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
expect(hookResult.activeTab).toBe('wallet');
|
|
39
|
+
expect(hookResult.availableTabIds).toEqual(['wallet', 'items']);
|
|
40
|
+
|
|
41
|
+
act(() => {
|
|
42
|
+
ReactDOM.render(
|
|
43
|
+
<TestComponent tabOrder={['wallet', 'items']} defaultActiveTab="wallet" />,
|
|
44
|
+
container
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
expect(hookResult.activeTab).toBe('items');
|
|
49
|
+
expect(hookResult.availableTabIds).toEqual(['items']);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('ignores tab changes for unavailable tabs', () => {
|
|
53
|
+
const onTabChange = jest.fn();
|
|
54
|
+
|
|
55
|
+
act(() => {
|
|
56
|
+
ReactDOM.render(
|
|
57
|
+
<TestComponent tabOrder={['items']} onTabChange={onTabChange} getItemCount={() => 3} />,
|
|
58
|
+
container
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
act(() => {
|
|
63
|
+
hookResult.handleTabChange('wallet');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
expect(hookResult.activeTab).toBe('items');
|
|
67
|
+
expect(onTabChange).not.toHaveBeenCalled();
|
|
68
|
+
});
|
|
69
|
+
});
|