@storybook/manager-api 7.0.19 → 7.0.21
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/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +9 -9
- package/src/tests/addons.test.js +167 -0
- package/src/tests/layout.test.js +168 -0
- package/src/tests/notifications.test.js +35 -0
- package/src/tests/shortcut.test.js +111 -0
- package/src/tests/shortcuts.test.js +287 -0
- package/src/tests/store.test.js +153 -0
- package/src/tests/url.test.js +211 -0
- package/src/tests/versions.test.js +397 -0
- package/src/typings.d.ts +14 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import store2 from 'store2';
|
|
2
|
+
import flushPromises from 'flush-promises';
|
|
3
|
+
|
|
4
|
+
import Store, { STORAGE_KEY } from '../store';
|
|
5
|
+
|
|
6
|
+
jest.mock('store2', () => ({
|
|
7
|
+
local: {
|
|
8
|
+
set: jest.fn(),
|
|
9
|
+
get: jest.fn(),
|
|
10
|
+
},
|
|
11
|
+
session: {
|
|
12
|
+
set: jest.fn(),
|
|
13
|
+
get: jest.fn(),
|
|
14
|
+
},
|
|
15
|
+
_: { fn: () => {} },
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
describe('store', () => {
|
|
19
|
+
it('sensibly combines local+session storage for initial state', () => {
|
|
20
|
+
store2.session.get.mockReturnValueOnce({ foo: 'bar', combined: { a: 'b' } });
|
|
21
|
+
store2.local.get.mockReturnValueOnce({ foo: 'baz', another: 'value', combined: { c: 'd' } });
|
|
22
|
+
|
|
23
|
+
const store = new Store({});
|
|
24
|
+
expect(store.getInitialState()).toEqual({
|
|
25
|
+
foo: 'bar',
|
|
26
|
+
another: 'value',
|
|
27
|
+
// We don't combine subfields from the two sources.
|
|
28
|
+
combined: { a: 'b' },
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('passes getState right through', () => {
|
|
33
|
+
const getState = jest.fn();
|
|
34
|
+
const store = new Store({ getState });
|
|
35
|
+
|
|
36
|
+
store.getState();
|
|
37
|
+
|
|
38
|
+
expect(getState).toHaveBeenCalled();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('setState', () => {
|
|
42
|
+
it('sets values in React only by default', async () => {
|
|
43
|
+
const setState = jest.fn().mockImplementation((x, cb) => cb());
|
|
44
|
+
const store = new Store({ setState });
|
|
45
|
+
|
|
46
|
+
await store.setState({ foo: 'bar' });
|
|
47
|
+
|
|
48
|
+
expect(setState).toHaveBeenCalledWith({ foo: 'bar' }, expect.any(Function));
|
|
49
|
+
expect(store2.session.set).not.toHaveBeenCalled();
|
|
50
|
+
expect(store2.local.set).not.toHaveBeenCalled();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('sets values in React and sessionStorage if persistence === session', async () => {
|
|
54
|
+
const setState = jest.fn().mockImplementation((x, cb) => cb());
|
|
55
|
+
const store = new Store({ setState });
|
|
56
|
+
|
|
57
|
+
await store.setState({ foo: 'bar' }, { persistence: 'session' });
|
|
58
|
+
|
|
59
|
+
expect(setState).toHaveBeenCalledWith({ foo: 'bar' }, expect.any(Function));
|
|
60
|
+
expect(store2.session.set).toHaveBeenCalledWith(STORAGE_KEY, { foo: 'bar' });
|
|
61
|
+
expect(store2.local.set).not.toHaveBeenCalled();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('sets values in React and sessionStorage if persistence === permanent', async () => {
|
|
65
|
+
const setState = jest.fn().mockImplementation((x, cb) => cb());
|
|
66
|
+
const store = new Store({ setState });
|
|
67
|
+
|
|
68
|
+
await store.setState({ foo: 'bar' }, { persistence: 'permanent' });
|
|
69
|
+
|
|
70
|
+
expect(setState).toHaveBeenCalledWith({ foo: 'bar' }, expect.any(Function));
|
|
71
|
+
expect(store2.session.set).not.toHaveBeenCalled();
|
|
72
|
+
expect(store2.local.set).toHaveBeenCalledWith(STORAGE_KEY, { foo: 'bar' });
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('properly patches existing values', async () => {
|
|
76
|
+
const setState = jest.fn().mockImplementation((x, cb) => cb());
|
|
77
|
+
store2.session.get.mockReturnValueOnce({
|
|
78
|
+
foo: 'baz',
|
|
79
|
+
another: 'value',
|
|
80
|
+
combined: { a: 'b' },
|
|
81
|
+
});
|
|
82
|
+
const store = new Store({ setState });
|
|
83
|
+
|
|
84
|
+
await store.setState({ foo: 'bar', combined: { c: 'd' } }, { persistence: 'session' });
|
|
85
|
+
|
|
86
|
+
expect(setState).toHaveBeenCalledWith(
|
|
87
|
+
{ foo: 'bar', combined: { c: 'd' } },
|
|
88
|
+
expect.any(Function)
|
|
89
|
+
);
|
|
90
|
+
expect(store2.session.set).toHaveBeenCalledWith(STORAGE_KEY, {
|
|
91
|
+
foo: 'bar',
|
|
92
|
+
another: 'value',
|
|
93
|
+
combined: { c: 'd' },
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('waits for react to setState', async () => {
|
|
98
|
+
let cb;
|
|
99
|
+
const setState = jest.fn().mockImplementation((x, inputCb) => {
|
|
100
|
+
cb = inputCb;
|
|
101
|
+
});
|
|
102
|
+
const store = new Store({ setState });
|
|
103
|
+
|
|
104
|
+
// NOTE: not awaiting here
|
|
105
|
+
let done = false;
|
|
106
|
+
store.setState({ foo: 'bar' }).then(() => {
|
|
107
|
+
done = true;
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
await flushPromises();
|
|
111
|
+
expect(setState).toHaveBeenCalledWith({ foo: 'bar' }, expect.any(Function));
|
|
112
|
+
expect(done).toBe(false);
|
|
113
|
+
|
|
114
|
+
cb();
|
|
115
|
+
await flushPromises();
|
|
116
|
+
expect(done).toBe(true);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('returns react.setState result', async () => {
|
|
120
|
+
const setState = jest.fn().mockImplementation((x, cb) => cb('RESULT'));
|
|
121
|
+
const store = new Store({ setState });
|
|
122
|
+
|
|
123
|
+
const result = await store.setState({ foo: 'bar' });
|
|
124
|
+
|
|
125
|
+
expect(result).toBe('RESULT');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('allows a callback', async () =>
|
|
129
|
+
new Promise((resolve) => {
|
|
130
|
+
const setState = jest.fn().mockImplementation((x, cb) => cb('RESULT'));
|
|
131
|
+
const store = new Store({ setState });
|
|
132
|
+
|
|
133
|
+
store.setState({ foo: 'bar' }, (result) => {
|
|
134
|
+
expect(result).toBe('RESULT');
|
|
135
|
+
resolve();
|
|
136
|
+
});
|
|
137
|
+
}));
|
|
138
|
+
|
|
139
|
+
it('allows a patch function and persists its results', async () => {
|
|
140
|
+
const setState = jest.fn().mockImplementation((x, cb) => {
|
|
141
|
+
x('OLD_STATE');
|
|
142
|
+
cb();
|
|
143
|
+
});
|
|
144
|
+
const store = new Store({ setState });
|
|
145
|
+
|
|
146
|
+
const patch = jest.fn().mockReturnValue({ foo: 'bar' });
|
|
147
|
+
await store.setState(patch, { persistence: 'session' });
|
|
148
|
+
|
|
149
|
+
expect(patch).toHaveBeenCalledWith('OLD_STATE');
|
|
150
|
+
expect(store2.session.set).toHaveBeenCalledWith(STORAGE_KEY, { foo: 'bar' });
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
});
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import qs from 'qs';
|
|
2
|
+
|
|
3
|
+
import { SET_CURRENT_STORY, GLOBALS_UPDATED, UPDATE_QUERY_PARAMS } from '@storybook/core-events';
|
|
4
|
+
|
|
5
|
+
import { init as initURL } from '../modules/url';
|
|
6
|
+
|
|
7
|
+
jest.mock('@storybook/client-logger');
|
|
8
|
+
jest.useFakeTimers();
|
|
9
|
+
|
|
10
|
+
describe('initial state', () => {
|
|
11
|
+
const viewMode = 'story';
|
|
12
|
+
|
|
13
|
+
describe('config query parameters', () => {
|
|
14
|
+
it('handles full parameter', () => {
|
|
15
|
+
const navigate = jest.fn();
|
|
16
|
+
const location = { search: qs.stringify({ full: '1' }) };
|
|
17
|
+
|
|
18
|
+
const {
|
|
19
|
+
state: { layout },
|
|
20
|
+
} = initURL({ navigate, state: { location } });
|
|
21
|
+
|
|
22
|
+
expect(layout).toEqual({ isFullscreen: true });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('handles nav parameter', () => {
|
|
26
|
+
const navigate = jest.fn();
|
|
27
|
+
const location = { search: qs.stringify({ nav: '0' }) };
|
|
28
|
+
|
|
29
|
+
const {
|
|
30
|
+
state: { layout },
|
|
31
|
+
} = initURL({ navigate, state: { location } });
|
|
32
|
+
|
|
33
|
+
expect(layout).toEqual({ showNav: false });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('handles shortcuts parameter', () => {
|
|
37
|
+
const navigate = jest.fn();
|
|
38
|
+
const location = { search: qs.stringify({ shortcuts: '0' }) };
|
|
39
|
+
|
|
40
|
+
const {
|
|
41
|
+
state: { ui },
|
|
42
|
+
} = initURL({ navigate, state: { location } });
|
|
43
|
+
|
|
44
|
+
expect(ui).toEqual({ enableShortcuts: false });
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('handles panel parameter, bottom', () => {
|
|
48
|
+
const navigate = jest.fn();
|
|
49
|
+
const location = { search: qs.stringify({ panel: 'bottom' }) };
|
|
50
|
+
|
|
51
|
+
const {
|
|
52
|
+
state: { layout },
|
|
53
|
+
} = initURL({ navigate, state: { location } });
|
|
54
|
+
|
|
55
|
+
expect(layout).toEqual({ panelPosition: 'bottom' });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('handles panel parameter, right', () => {
|
|
59
|
+
const navigate = jest.fn();
|
|
60
|
+
const location = { search: qs.stringify({ panel: 'right' }) };
|
|
61
|
+
|
|
62
|
+
const {
|
|
63
|
+
state: { layout },
|
|
64
|
+
} = initURL({ navigate, state: { location } });
|
|
65
|
+
|
|
66
|
+
expect(layout).toEqual({ panelPosition: 'right' });
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('handles panel parameter, 0', () => {
|
|
70
|
+
const navigate = jest.fn();
|
|
71
|
+
const location = { search: qs.stringify({ panel: '0' }) };
|
|
72
|
+
|
|
73
|
+
const {
|
|
74
|
+
state: { layout },
|
|
75
|
+
} = initURL({ navigate, state: { location } });
|
|
76
|
+
|
|
77
|
+
expect(layout).toEqual({ showPanel: false });
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe('queryParams', () => {
|
|
83
|
+
it('lets your read out parameters you set previously', () => {
|
|
84
|
+
let state = {};
|
|
85
|
+
const store = {
|
|
86
|
+
setState: (change) => {
|
|
87
|
+
state = { ...state, ...change };
|
|
88
|
+
},
|
|
89
|
+
getState: () => state,
|
|
90
|
+
};
|
|
91
|
+
const fullAPI = { emit: jest.fn() };
|
|
92
|
+
const { api } = initURL({
|
|
93
|
+
state: { location: { search: '' } },
|
|
94
|
+
navigate: jest.fn(),
|
|
95
|
+
store,
|
|
96
|
+
fullAPI,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
api.setQueryParams({ foo: 'bar' });
|
|
100
|
+
|
|
101
|
+
expect(api.getQueryParam('foo')).toEqual('bar');
|
|
102
|
+
expect(fullAPI.emit).toHaveBeenCalledWith(UPDATE_QUERY_PARAMS, { foo: 'bar' });
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe('initModule', () => {
|
|
107
|
+
const store = {
|
|
108
|
+
state: {},
|
|
109
|
+
getState() {
|
|
110
|
+
return this.state;
|
|
111
|
+
},
|
|
112
|
+
setState(value) {
|
|
113
|
+
this.state = { ...this.state, ...value };
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
const storyState = (storyId) => ({
|
|
117
|
+
path: `/story/${storyId}`,
|
|
118
|
+
storyId,
|
|
119
|
+
viewMode: 'story',
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const fullAPI = {
|
|
123
|
+
callbacks: {},
|
|
124
|
+
on(event, fn) {
|
|
125
|
+
this.callbacks[event] = this.callbacks[event] || [];
|
|
126
|
+
this.callbacks[event].push(fn);
|
|
127
|
+
},
|
|
128
|
+
emit(event, ...args) {
|
|
129
|
+
this.callbacks[event]?.forEach((cb) => cb(...args));
|
|
130
|
+
},
|
|
131
|
+
showReleaseNotesOnLaunch: jest.fn(),
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
beforeEach(() => {
|
|
135
|
+
store.state = {};
|
|
136
|
+
fullAPI.callbacks = {};
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('updates args param on SET_CURRENT_STORY', async () => {
|
|
140
|
+
store.setState(storyState('test--story'));
|
|
141
|
+
|
|
142
|
+
const navigate = jest.fn();
|
|
143
|
+
|
|
144
|
+
const { api, init } = initURL({ store, state: { location: {} }, navigate, fullAPI });
|
|
145
|
+
Object.assign(fullAPI, api, {
|
|
146
|
+
getCurrentStoryData: () => ({
|
|
147
|
+
type: 'story',
|
|
148
|
+
args: { a: 1, b: 2 },
|
|
149
|
+
initialArgs: { a: 1, b: 1 },
|
|
150
|
+
isLeaf: true,
|
|
151
|
+
}),
|
|
152
|
+
});
|
|
153
|
+
init();
|
|
154
|
+
|
|
155
|
+
fullAPI.emit(SET_CURRENT_STORY);
|
|
156
|
+
expect(navigate).toHaveBeenCalledWith(
|
|
157
|
+
'/story/test--story&args=b:2',
|
|
158
|
+
expect.objectContaining({ replace: true })
|
|
159
|
+
);
|
|
160
|
+
expect(store.getState().customQueryParams).toEqual({ args: 'b:2' });
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('updates globals param on GLOBALS_UPDATED', async () => {
|
|
164
|
+
store.setState(storyState('test--story'));
|
|
165
|
+
|
|
166
|
+
const navigate = jest.fn();
|
|
167
|
+
|
|
168
|
+
const { api, init } = initURL({ store, state: { location: {} }, navigate, fullAPI });
|
|
169
|
+
Object.assign(fullAPI, api);
|
|
170
|
+
init();
|
|
171
|
+
|
|
172
|
+
fullAPI.emit(GLOBALS_UPDATED, { globals: { a: 2 }, initialGlobals: { a: 1, b: 1 } });
|
|
173
|
+
expect(navigate).toHaveBeenCalledWith(
|
|
174
|
+
'/story/test--story&globals=a:2;b:!undefined',
|
|
175
|
+
expect.objectContaining({ replace: true })
|
|
176
|
+
);
|
|
177
|
+
expect(store.getState().customQueryParams).toEqual({ globals: 'a:2;b:!undefined' });
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('adds url params alphabetically', async () => {
|
|
181
|
+
store.setState({ ...storyState('test--story'), customQueryParams: { full: 1 } });
|
|
182
|
+
const navigate = jest.fn();
|
|
183
|
+
|
|
184
|
+
const { api, init } = initURL({ store, state: { location: {} }, navigate, fullAPI });
|
|
185
|
+
Object.assign(fullAPI, api, {
|
|
186
|
+
getCurrentStoryData: () => ({ type: 'story', args: { a: 1 }, isLeaf: true }),
|
|
187
|
+
});
|
|
188
|
+
init();
|
|
189
|
+
|
|
190
|
+
fullAPI.emit(GLOBALS_UPDATED, { globals: { g: 2 } });
|
|
191
|
+
expect(navigate).toHaveBeenCalledWith(
|
|
192
|
+
'/story/test--story&full=1&globals=g:2',
|
|
193
|
+
expect.objectContaining({ replace: true })
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
fullAPI.emit(SET_CURRENT_STORY);
|
|
197
|
+
expect(navigate).toHaveBeenCalledWith(
|
|
198
|
+
'/story/test--story&args=a:1&full=1&globals=g:2',
|
|
199
|
+
expect.objectContaining({ replace: true })
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('navigates to release notes when needed', () => {
|
|
204
|
+
fullAPI.showReleaseNotesOnLaunch.mockReturnValueOnce(true);
|
|
205
|
+
|
|
206
|
+
const navigate = jest.fn();
|
|
207
|
+
initURL({ store, state: { location: {} }, navigate, fullAPI }).init();
|
|
208
|
+
|
|
209
|
+
expect(navigate).toHaveBeenCalledWith('/settings/release-notes');
|
|
210
|
+
});
|
|
211
|
+
});
|