zero-query 0.9.9 → 1.0.0
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/README.md +33 -32
- package/cli/args.js +1 -1
- package/cli/commands/build.js +2 -2
- package/cli/commands/bundle.js +15 -15
- package/cli/commands/create.js +2 -2
- package/cli/commands/dev/devtools/index.js +1 -1
- package/cli/commands/dev/devtools/js/core.js +14 -14
- package/cli/commands/dev/devtools/js/elements.js +4 -4
- package/cli/commands/dev/devtools/js/stats.js +1 -1
- package/cli/commands/dev/devtools/styles.css +2 -2
- package/cli/commands/dev/index.js +2 -2
- package/cli/commands/dev/logger.js +1 -1
- package/cli/commands/dev/overlay.js +21 -14
- package/cli/commands/dev/server.js +5 -5
- package/cli/commands/dev/validator.js +7 -7
- package/cli/commands/dev/watcher.js +6 -6
- package/cli/help.js +3 -3
- package/cli/index.js +2 -2
- package/cli/scaffold/default/app/app.js +17 -18
- package/cli/scaffold/default/app/components/about.js +9 -9
- package/cli/scaffold/default/app/components/api-demo.js +6 -6
- package/cli/scaffold/default/app/components/contact-card.js +4 -4
- package/cli/scaffold/default/app/components/contacts/contacts.css +2 -2
- package/cli/scaffold/default/app/components/contacts/contacts.html +3 -3
- package/cli/scaffold/default/app/components/contacts/contacts.js +11 -11
- package/cli/scaffold/default/app/components/counter.js +8 -8
- package/cli/scaffold/default/app/components/home.js +13 -13
- package/cli/scaffold/default/app/components/not-found.js +1 -1
- package/cli/scaffold/default/app/components/playground/playground.css +1 -1
- package/cli/scaffold/default/app/components/playground/playground.html +11 -11
- package/cli/scaffold/default/app/components/playground/playground.js +11 -11
- package/cli/scaffold/default/app/components/todos.js +8 -8
- package/cli/scaffold/default/app/components/toolkit/toolkit.css +1 -1
- package/cli/scaffold/default/app/components/toolkit/toolkit.html +4 -4
- package/cli/scaffold/default/app/components/toolkit/toolkit.js +7 -7
- package/cli/scaffold/default/app/routes.js +1 -1
- package/cli/scaffold/default/app/store.js +1 -1
- package/cli/scaffold/default/global.css +2 -2
- package/cli/scaffold/default/index.html +2 -2
- package/cli/scaffold/minimal/app/app.js +6 -7
- package/cli/scaffold/minimal/app/components/about.js +5 -5
- package/cli/scaffold/minimal/app/components/counter.js +6 -6
- package/cli/scaffold/minimal/app/components/home.js +8 -8
- package/cli/scaffold/minimal/app/components/not-found.js +1 -1
- package/cli/scaffold/minimal/app/routes.js +1 -1
- package/cli/scaffold/minimal/app/store.js +1 -1
- package/cli/scaffold/minimal/global.css +2 -2
- package/cli/scaffold/minimal/index.html +1 -1
- package/cli/scaffold/ssr/app/app.js +1 -2
- package/cli/scaffold/ssr/app/components/about.js +5 -5
- package/cli/scaffold/ssr/app/components/home.js +2 -2
- package/cli/scaffold/ssr/app/components/not-found.js +1 -1
- package/cli/scaffold/ssr/app/routes.js +1 -1
- package/cli/scaffold/ssr/global.css +2 -2
- package/cli/scaffold/ssr/index.html +2 -2
- package/cli/scaffold/ssr/server/index.js +4 -4
- package/cli/utils.js +6 -6
- package/dist/zquery.dist.zip +0 -0
- package/dist/zquery.js +508 -227
- package/dist/zquery.min.js +2 -2
- package/index.d.ts +16 -13
- package/index.js +7 -5
- package/package.json +2 -2
- package/src/component.js +64 -63
- package/src/core.js +15 -15
- package/src/diff.js +38 -38
- package/src/errors.js +17 -17
- package/src/expression.js +15 -17
- package/src/http.js +4 -4
- package/src/reactive.js +75 -9
- package/src/router.js +104 -24
- package/src/ssr.js +28 -28
- package/src/store.js +103 -21
- package/src/utils.js +64 -12
- package/tests/audit.test.js +143 -15
- package/tests/cli.test.js +20 -20
- package/tests/component.test.js +121 -121
- package/tests/core.test.js +56 -56
- package/tests/diff.test.js +42 -42
- package/tests/errors.test.js +5 -5
- package/tests/expression.test.js +58 -53
- package/tests/http.test.js +20 -20
- package/tests/reactive.test.js +185 -24
- package/tests/router.test.js +501 -74
- package/tests/ssr.test.js +15 -13
- package/tests/store.test.js +264 -23
- package/tests/utils.test.js +163 -26
- package/types/collection.d.ts +2 -2
- package/types/component.d.ts +5 -5
- package/types/errors.d.ts +3 -3
- package/types/http.d.ts +3 -3
- package/types/misc.d.ts +9 -9
- package/types/reactive.d.ts +25 -3
- package/types/router.d.ts +10 -6
- package/types/ssr.d.ts +2 -2
- package/types/store.d.ts +40 -5
- package/types/utils.d.ts +1 -1
package/tests/ssr.test.js
CHANGED
|
@@ -30,7 +30,7 @@ describe('createSSRApp', () => {
|
|
|
30
30
|
// component registration
|
|
31
31
|
// ---------------------------------------------------------------------------
|
|
32
32
|
|
|
33
|
-
describe('SSRApp
|
|
33
|
+
describe('SSRApp - component', () => {
|
|
34
34
|
it('registers a component and returns self for chaining', () => {
|
|
35
35
|
const app = createSSRApp();
|
|
36
36
|
const result = app.component('my-comp', {
|
|
@@ -80,7 +80,7 @@ describe('SSRApp — component', () => {
|
|
|
80
80
|
// SSRApp.has()
|
|
81
81
|
// ---------------------------------------------------------------------------
|
|
82
82
|
|
|
83
|
-
describe('SSRApp
|
|
83
|
+
describe('SSRApp - has', () => {
|
|
84
84
|
it('returns false for unregistered', () => {
|
|
85
85
|
const app = createSSRApp();
|
|
86
86
|
expect(app.has('nope')).toBe(false);
|
|
@@ -98,7 +98,7 @@ describe('SSRApp — has', () => {
|
|
|
98
98
|
// renderToString (app method)
|
|
99
99
|
// ---------------------------------------------------------------------------
|
|
100
100
|
|
|
101
|
-
describe('SSRApp
|
|
101
|
+
describe('SSRApp - renderToString', () => {
|
|
102
102
|
it('renders basic component', async () => {
|
|
103
103
|
const app = createSSRApp();
|
|
104
104
|
app.component('my-page', {
|
|
@@ -193,7 +193,8 @@ describe('SSRApp — renderToString', () => {
|
|
|
193
193
|
render() { throw new Error('render boom'); }
|
|
194
194
|
});
|
|
195
195
|
const html = await app.renderToString('bad');
|
|
196
|
-
expect(html).toContain('<!-- SSR render error
|
|
196
|
+
expect(html).toContain('<!-- SSR render error -->');
|
|
197
|
+
expect(html).not.toContain('render boom');
|
|
197
198
|
expect(handler).toHaveBeenCalled();
|
|
198
199
|
const err = handler.mock.calls[0][0];
|
|
199
200
|
expect(err.code).toBe(ErrorCode.SSR_RENDER);
|
|
@@ -267,7 +268,7 @@ describe('renderToString (standalone)', () => {
|
|
|
267
268
|
// State factory
|
|
268
269
|
// ---------------------------------------------------------------------------
|
|
269
270
|
|
|
270
|
-
describe('SSR
|
|
271
|
+
describe('SSR - state factory', () => {
|
|
271
272
|
it('calls state function for initial state', async () => {
|
|
272
273
|
const app = createSSRApp();
|
|
273
274
|
let callCount = 0;
|
|
@@ -328,7 +329,7 @@ describe('SSR — state factory', () => {
|
|
|
328
329
|
// Computed properties
|
|
329
330
|
// ---------------------------------------------------------------------------
|
|
330
331
|
|
|
331
|
-
describe('SSR
|
|
332
|
+
describe('SSR - computed', () => {
|
|
332
333
|
it('computes derived values', async () => {
|
|
333
334
|
const app = createSSRApp();
|
|
334
335
|
app.component('comp', {
|
|
@@ -382,7 +383,7 @@ describe('SSR — computed', () => {
|
|
|
382
383
|
// User methods
|
|
383
384
|
// ---------------------------------------------------------------------------
|
|
384
385
|
|
|
385
|
-
describe('SSR
|
|
386
|
+
describe('SSR - user methods', () => {
|
|
386
387
|
it('binds user methods and they can be called in render', async () => {
|
|
387
388
|
const app = createSSRApp();
|
|
388
389
|
app.component('comp', {
|
|
@@ -409,7 +410,7 @@ describe('SSR — user methods', () => {
|
|
|
409
410
|
// Init lifecycle
|
|
410
411
|
// ---------------------------------------------------------------------------
|
|
411
412
|
|
|
412
|
-
describe('SSR
|
|
413
|
+
describe('SSR - init lifecycle', () => {
|
|
413
414
|
it('calls init() during construction', () => {
|
|
414
415
|
let initCalled = false;
|
|
415
416
|
renderToString({
|
|
@@ -445,7 +446,7 @@ describe('SSR — init lifecycle', () => {
|
|
|
445
446
|
// {{expression}} interpolation
|
|
446
447
|
// ---------------------------------------------------------------------------
|
|
447
448
|
|
|
448
|
-
describe('SSR
|
|
449
|
+
describe('SSR - expression interpolation', () => {
|
|
449
450
|
it('interpolates {{state.key}} patterns', () => {
|
|
450
451
|
const html = renderToString({
|
|
451
452
|
state: () => ({ name: 'World' }),
|
|
@@ -493,7 +494,7 @@ describe('SSR — expression interpolation', () => {
|
|
|
493
494
|
// XSS sanitization via _escapeHtml
|
|
494
495
|
// ---------------------------------------------------------------------------
|
|
495
496
|
|
|
496
|
-
describe('SSR
|
|
497
|
+
describe('SSR - XSS sanitization', () => {
|
|
497
498
|
it('escapes HTML in renderPage title', async () => {
|
|
498
499
|
const app = createSSRApp();
|
|
499
500
|
const html = await app.renderPage({ title: '<script>alert("xss")</script>' });
|
|
@@ -539,7 +540,7 @@ describe('SSR — XSS sanitization', () => {
|
|
|
539
540
|
// renderPage
|
|
540
541
|
// ---------------------------------------------------------------------------
|
|
541
542
|
|
|
542
|
-
describe('SSRApp
|
|
543
|
+
describe('SSRApp - renderPage', () => {
|
|
543
544
|
it('renders a full HTML page', async () => {
|
|
544
545
|
const app = createSSRApp();
|
|
545
546
|
app.component('page', { render() { return '<h1>Home</h1>'; } });
|
|
@@ -605,7 +606,8 @@ describe('SSRApp — renderPage', () => {
|
|
|
605
606
|
app.component('bad-page', { render() { throw new Error('page boom'); } });
|
|
606
607
|
const html = await app.renderPage({ component: 'bad-page', title: 'Oops' });
|
|
607
608
|
expect(html).toContain('<!DOCTYPE html>');
|
|
608
|
-
expect(html).toContain('<!-- SSR render error
|
|
609
|
+
expect(html).toContain('<!-- SSR render error -->');
|
|
610
|
+
expect(html).not.toContain('page boom');
|
|
609
611
|
expect(handler).toHaveBeenCalled();
|
|
610
612
|
|
|
611
613
|
spy.mockRestore();
|
|
@@ -636,7 +638,7 @@ describe('SSRApp — renderPage', () => {
|
|
|
636
638
|
// renderBatch
|
|
637
639
|
// ---------------------------------------------------------------------------
|
|
638
640
|
|
|
639
|
-
describe('SSRApp
|
|
641
|
+
describe('SSRApp - renderBatch', () => {
|
|
640
642
|
it('renders multiple components at once', async () => {
|
|
641
643
|
const app = createSSRApp();
|
|
642
644
|
app.component('header-el', { render() { return '<header>Head</header>'; } });
|
package/tests/store.test.js
CHANGED
|
@@ -6,7 +6,7 @@ import { createStore, getStore } from '../src/store.js';
|
|
|
6
6
|
// Store creation
|
|
7
7
|
// ---------------------------------------------------------------------------
|
|
8
8
|
|
|
9
|
-
describe('Store
|
|
9
|
+
describe('Store - creation', () => {
|
|
10
10
|
it('creates a store with initial state', () => {
|
|
11
11
|
const store = createStore('test-create', {
|
|
12
12
|
state: { count: 0, name: 'Tony' },
|
|
@@ -42,7 +42,7 @@ describe('Store — creation', () => {
|
|
|
42
42
|
// Dispatch & actions
|
|
43
43
|
// ---------------------------------------------------------------------------
|
|
44
44
|
|
|
45
|
-
describe('Store
|
|
45
|
+
describe('Store - dispatch', () => {
|
|
46
46
|
it('dispatches a named action', () => {
|
|
47
47
|
const store = createStore('dispatch-1', {
|
|
48
48
|
state: { count: 0 },
|
|
@@ -101,7 +101,7 @@ describe('Store — dispatch', () => {
|
|
|
101
101
|
// Subscriptions
|
|
102
102
|
// ---------------------------------------------------------------------------
|
|
103
103
|
|
|
104
|
-
describe('Store
|
|
104
|
+
describe('Store - subscribe', () => {
|
|
105
105
|
it('notifies key-specific subscribers', () => {
|
|
106
106
|
const store = createStore('sub-1', {
|
|
107
107
|
state: { count: 0 },
|
|
@@ -110,7 +110,7 @@ describe('Store — subscribe', () => {
|
|
|
110
110
|
const fn = vi.fn();
|
|
111
111
|
store.subscribe('count', fn);
|
|
112
112
|
store.dispatch('inc');
|
|
113
|
-
expect(fn).toHaveBeenCalledWith(1, 0
|
|
113
|
+
expect(fn).toHaveBeenCalledWith('count', 1, 0);
|
|
114
114
|
});
|
|
115
115
|
|
|
116
116
|
it('wildcard subscriber gets all changes', () => {
|
|
@@ -157,7 +157,7 @@ describe('Store — subscribe', () => {
|
|
|
157
157
|
// Getters
|
|
158
158
|
// ---------------------------------------------------------------------------
|
|
159
159
|
|
|
160
|
-
describe('Store
|
|
160
|
+
describe('Store - getters', () => {
|
|
161
161
|
it('computes values from state', () => {
|
|
162
162
|
const store = createStore('getters-1', {
|
|
163
163
|
state: { count: 5 },
|
|
@@ -184,7 +184,7 @@ describe('Store — getters', () => {
|
|
|
184
184
|
// Middleware
|
|
185
185
|
// ---------------------------------------------------------------------------
|
|
186
186
|
|
|
187
|
-
describe('Store
|
|
187
|
+
describe('Store - middleware', () => {
|
|
188
188
|
it('calls middleware before action', () => {
|
|
189
189
|
const log = vi.fn();
|
|
190
190
|
const store = createStore('mw-1', {
|
|
@@ -223,7 +223,7 @@ describe('Store — middleware', () => {
|
|
|
223
223
|
// Snapshot & replaceState
|
|
224
224
|
// ---------------------------------------------------------------------------
|
|
225
225
|
|
|
226
|
-
describe('Store
|
|
226
|
+
describe('Store - snapshot & replaceState', () => {
|
|
227
227
|
it('snapshot returns plain object copy', () => {
|
|
228
228
|
const store = createStore('snap-1', { state: { a: 1, b: { c: 2 } } });
|
|
229
229
|
const snap = store.snapshot();
|
|
@@ -257,7 +257,7 @@ describe('Store — snapshot & replaceState', () => {
|
|
|
257
257
|
// Multiple middleware
|
|
258
258
|
// ---------------------------------------------------------------------------
|
|
259
259
|
|
|
260
|
-
describe('Store
|
|
260
|
+
describe('Store - multiple middleware', () => {
|
|
261
261
|
it('runs middleware in order', () => {
|
|
262
262
|
const order = [];
|
|
263
263
|
const store = createStore('mw-multi', {
|
|
@@ -287,7 +287,7 @@ describe('Store — multiple middleware', () => {
|
|
|
287
287
|
// Async actions
|
|
288
288
|
// ---------------------------------------------------------------------------
|
|
289
289
|
|
|
290
|
-
describe('Store
|
|
290
|
+
describe('Store - async actions', () => {
|
|
291
291
|
it('supports async action returning promise', async () => {
|
|
292
292
|
const store = createStore('async-1', {
|
|
293
293
|
state: { data: null },
|
|
@@ -307,7 +307,7 @@ describe('Store — async actions', () => {
|
|
|
307
307
|
// Subscriber deduplication
|
|
308
308
|
// ---------------------------------------------------------------------------
|
|
309
309
|
|
|
310
|
-
describe('Store
|
|
310
|
+
describe('Store - subscriber edge cases', () => {
|
|
311
311
|
it('same function subscribed twice fires twice', () => {
|
|
312
312
|
const store = createStore('sub-dedup', {
|
|
313
313
|
state: { x: 0 },
|
|
@@ -340,7 +340,7 @@ describe('Store — subscriber edge cases', () => {
|
|
|
340
340
|
// Action return value
|
|
341
341
|
// ---------------------------------------------------------------------------
|
|
342
342
|
|
|
343
|
-
describe('Store
|
|
343
|
+
describe('Store - action return value', () => {
|
|
344
344
|
it('dispatch returns action result', () => {
|
|
345
345
|
const store = createStore('ret-1', {
|
|
346
346
|
state: { x: 0 },
|
|
@@ -355,7 +355,7 @@ describe('Store — action return value', () => {
|
|
|
355
355
|
// Getters with multiple state keys
|
|
356
356
|
// ---------------------------------------------------------------------------
|
|
357
357
|
|
|
358
|
-
describe('Store
|
|
358
|
+
describe('Store - complex getters', () => {
|
|
359
359
|
it('getter uses multiple state keys', () => {
|
|
360
360
|
const store = createStore('getter-multi', {
|
|
361
361
|
state: { firstName: 'Tony', lastName: 'W' },
|
|
@@ -383,7 +383,7 @@ describe('Store — complex getters', () => {
|
|
|
383
383
|
// PERF: history trim uses splice (in-place) instead of slice (copy)
|
|
384
384
|
// ---------------------------------------------------------------------------
|
|
385
385
|
|
|
386
|
-
describe('Store
|
|
386
|
+
describe('Store - history trim in-place', () => {
|
|
387
387
|
it('trims history to maxHistory without exceeding', () => {
|
|
388
388
|
const store = createStore('hist-trim', {
|
|
389
389
|
state: { n: 0 },
|
|
@@ -412,10 +412,10 @@ describe('Store — history trim in-place', () => {
|
|
|
412
412
|
|
|
413
413
|
|
|
414
414
|
// ===========================================================================
|
|
415
|
-
// use()
|
|
415
|
+
// use() - middleware chaining
|
|
416
416
|
// ===========================================================================
|
|
417
417
|
|
|
418
|
-
describe('Store
|
|
418
|
+
describe('Store - use() chaining', () => {
|
|
419
419
|
it('returns the store for chaining', () => {
|
|
420
420
|
const store = createStore({
|
|
421
421
|
state: { x: 0 },
|
|
@@ -453,7 +453,7 @@ describe('Store — use() chaining', () => {
|
|
|
453
453
|
// debug mode
|
|
454
454
|
// ===========================================================================
|
|
455
455
|
|
|
456
|
-
describe('Store
|
|
456
|
+
describe('Store - debug mode', () => {
|
|
457
457
|
it('logs when debug is true', () => {
|
|
458
458
|
const spy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
459
459
|
const store = createStore({
|
|
@@ -474,7 +474,7 @@ describe('Store — debug mode', () => {
|
|
|
474
474
|
// replaceState
|
|
475
475
|
// ===========================================================================
|
|
476
476
|
|
|
477
|
-
describe('Store
|
|
477
|
+
describe('Store - replaceState', () => {
|
|
478
478
|
it('replaces all keys', () => {
|
|
479
479
|
const store = createStore({
|
|
480
480
|
state: { a: 1, b: 2 }
|
|
@@ -492,7 +492,7 @@ describe('Store — replaceState', () => {
|
|
|
492
492
|
// wildcard subscription
|
|
493
493
|
// ===========================================================================
|
|
494
494
|
|
|
495
|
-
describe('Store
|
|
495
|
+
describe('Store - wildcard subscription', () => {
|
|
496
496
|
it('fires on any state change', () => {
|
|
497
497
|
const store = createStore({
|
|
498
498
|
state: { a: 1, b: 2 },
|
|
@@ -528,7 +528,7 @@ describe('Store — wildcard subscription', () => {
|
|
|
528
528
|
// state as factory function
|
|
529
529
|
// ===========================================================================
|
|
530
530
|
|
|
531
|
-
describe('Store
|
|
531
|
+
describe('Store - state factory', () => {
|
|
532
532
|
it('calls state function for initial state', () => {
|
|
533
533
|
const store = createStore({
|
|
534
534
|
state: () => ({ count: 0 })
|
|
@@ -539,10 +539,10 @@ describe('Store — state factory', () => {
|
|
|
539
539
|
|
|
540
540
|
|
|
541
541
|
// ===========================================================================
|
|
542
|
-
// createStore
|
|
542
|
+
// createStore - named stores
|
|
543
543
|
// ===========================================================================
|
|
544
544
|
|
|
545
|
-
describe('createStore
|
|
545
|
+
describe('createStore - named stores', () => {
|
|
546
546
|
it('creates default store when no name given', () => {
|
|
547
547
|
const store = createStore({ state: { x: 1 } });
|
|
548
548
|
expect(store.state.x).toBe(1);
|
|
@@ -559,7 +559,7 @@ describe('createStore — named stores', () => {
|
|
|
559
559
|
// reset
|
|
560
560
|
// ===========================================================================
|
|
561
561
|
|
|
562
|
-
describe('Store
|
|
562
|
+
describe('Store - reset', () => {
|
|
563
563
|
it('resets state and clears history', () => {
|
|
564
564
|
const store = createStore({
|
|
565
565
|
state: { x: 0 },
|
|
@@ -580,10 +580,251 @@ describe('Store — reset', () => {
|
|
|
580
580
|
// empty config
|
|
581
581
|
// ===========================================================================
|
|
582
582
|
|
|
583
|
-
describe('Store
|
|
583
|
+
describe('Store - empty config', () => {
|
|
584
584
|
it('creates store with no config', () => {
|
|
585
585
|
const store = createStore({});
|
|
586
586
|
expect(store.snapshot()).toEqual({});
|
|
587
587
|
expect(store.history).toEqual([]);
|
|
588
588
|
});
|
|
589
589
|
});
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
// ===========================================================================
|
|
593
|
+
// Store - batch
|
|
594
|
+
// ===========================================================================
|
|
595
|
+
|
|
596
|
+
describe('Store - batch', () => {
|
|
597
|
+
it('fires subscribers once per key, not per mutation', () => {
|
|
598
|
+
const store = createStore({
|
|
599
|
+
state: { x: 0, y: 0 },
|
|
600
|
+
actions: {
|
|
601
|
+
setX(state, v) { state.x = v; },
|
|
602
|
+
setY(state, v) { state.y = v; },
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
const fn = vi.fn();
|
|
606
|
+
store.subscribe('x', fn);
|
|
607
|
+
|
|
608
|
+
store.batch(state => {
|
|
609
|
+
state.x = 1;
|
|
610
|
+
state.x = 2;
|
|
611
|
+
state.x = 3;
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
// Should fire once with the final value
|
|
615
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
616
|
+
expect(store.state.x).toBe(3);
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
it('batches changes across multiple keys', () => {
|
|
620
|
+
const store = createStore({
|
|
621
|
+
state: { a: 0, b: 0 }
|
|
622
|
+
});
|
|
623
|
+
const fnA = vi.fn();
|
|
624
|
+
const fnB = vi.fn();
|
|
625
|
+
store.subscribe('a', fnA);
|
|
626
|
+
store.subscribe('b', fnB);
|
|
627
|
+
|
|
628
|
+
store.batch(state => {
|
|
629
|
+
state.a = 10;
|
|
630
|
+
state.b = 20;
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
expect(fnA).toHaveBeenCalledTimes(1);
|
|
634
|
+
expect(fnB).toHaveBeenCalledTimes(1);
|
|
635
|
+
expect(store.state.a).toBe(10);
|
|
636
|
+
expect(store.state.b).toBe(20);
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
it('does not fire subscribers during the batch', () => {
|
|
640
|
+
const store = createStore({ state: { x: 0 } });
|
|
641
|
+
const calls = [];
|
|
642
|
+
store.subscribe('x', (val) => calls.push(val));
|
|
643
|
+
|
|
644
|
+
store.batch(state => {
|
|
645
|
+
state.x = 1;
|
|
646
|
+
// Subscriber should not have been called yet
|
|
647
|
+
expect(calls.length).toBe(0);
|
|
648
|
+
state.x = 2;
|
|
649
|
+
expect(calls.length).toBe(0);
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
// Now it fires
|
|
653
|
+
expect(calls.length).toBe(1);
|
|
654
|
+
});
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
// ===========================================================================
|
|
659
|
+
// Store - checkpoint / undo / redo
|
|
660
|
+
// ===========================================================================
|
|
661
|
+
|
|
662
|
+
describe('Store - checkpoint / undo / redo', () => {
|
|
663
|
+
it('undo restores to checkpointed state', () => {
|
|
664
|
+
const store = createStore({
|
|
665
|
+
state: { count: 0 },
|
|
666
|
+
actions: { inc(state) { state.count++; } }
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
store.checkpoint();
|
|
670
|
+
store.dispatch('inc');
|
|
671
|
+
store.dispatch('inc');
|
|
672
|
+
expect(store.state.count).toBe(2);
|
|
673
|
+
|
|
674
|
+
const didUndo = store.undo();
|
|
675
|
+
expect(didUndo).toBe(true);
|
|
676
|
+
expect(store.state.count).toBe(0);
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
it('redo restores the undone state', () => {
|
|
680
|
+
const store = createStore({
|
|
681
|
+
state: { count: 0 },
|
|
682
|
+
actions: { inc(state) { state.count++; } }
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
store.checkpoint();
|
|
686
|
+
store.dispatch('inc');
|
|
687
|
+
store.dispatch('inc');
|
|
688
|
+
store.undo();
|
|
689
|
+
expect(store.state.count).toBe(0);
|
|
690
|
+
|
|
691
|
+
const didRedo = store.redo();
|
|
692
|
+
expect(didRedo).toBe(true);
|
|
693
|
+
expect(store.state.count).toBe(2);
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
it('undo returns false when no checkpoints', () => {
|
|
697
|
+
const store = createStore({ state: { x: 1 } });
|
|
698
|
+
expect(store.undo()).toBe(false);
|
|
699
|
+
expect(store.state.x).toBe(1);
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
it('redo returns false when nothing to redo', () => {
|
|
703
|
+
const store = createStore({ state: { x: 1 } });
|
|
704
|
+
expect(store.redo()).toBe(false);
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
it('canUndo and canRedo reflect stack state', () => {
|
|
708
|
+
const store = createStore({
|
|
709
|
+
state: { v: 'a' },
|
|
710
|
+
actions: { set(state, v) { state.v = v; } }
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
expect(store.canUndo).toBe(false);
|
|
714
|
+
expect(store.canRedo).toBe(false);
|
|
715
|
+
|
|
716
|
+
store.checkpoint();
|
|
717
|
+
expect(store.canUndo).toBe(true);
|
|
718
|
+
|
|
719
|
+
store.dispatch('set', 'b');
|
|
720
|
+
store.undo();
|
|
721
|
+
expect(store.canRedo).toBe(true);
|
|
722
|
+
|
|
723
|
+
store.redo();
|
|
724
|
+
expect(store.canRedo).toBe(false);
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
it('new checkpoint clears redo stack', () => {
|
|
728
|
+
const store = createStore({
|
|
729
|
+
state: { x: 0 },
|
|
730
|
+
actions: { set(state, v) { state.x = v; } }
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
store.checkpoint();
|
|
734
|
+
store.dispatch('set', 1);
|
|
735
|
+
store.undo();
|
|
736
|
+
expect(store.canRedo).toBe(true);
|
|
737
|
+
|
|
738
|
+
// New checkpoint clears redo
|
|
739
|
+
store.checkpoint();
|
|
740
|
+
expect(store.canRedo).toBe(false);
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
it('respects maxUndo limit', () => {
|
|
744
|
+
const store = createStore({
|
|
745
|
+
state: { x: 0 },
|
|
746
|
+
maxUndo: 3,
|
|
747
|
+
actions: { set(state, v) { state.x = v; } }
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
store.checkpoint(); // save x=0
|
|
751
|
+
store.dispatch('set', 1);
|
|
752
|
+
store.checkpoint(); // save x=1
|
|
753
|
+
store.dispatch('set', 2);
|
|
754
|
+
store.checkpoint(); // save x=2
|
|
755
|
+
store.dispatch('set', 3);
|
|
756
|
+
store.checkpoint(); // save x=3 -> should trim oldest (x=0)
|
|
757
|
+
store.dispatch('set', 4);
|
|
758
|
+
|
|
759
|
+
// Should have at most 3 entries
|
|
760
|
+
store.undo(); // -> x=3
|
|
761
|
+
store.undo(); // -> x=2
|
|
762
|
+
store.undo(); // -> x=1
|
|
763
|
+
expect(store.undo()).toBe(false); // oldest was trimmed
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
it('multiple undo/redo cycles', () => {
|
|
767
|
+
const store = createStore({
|
|
768
|
+
state: { n: 0 },
|
|
769
|
+
actions: { set(state, v) { state.n = v; } }
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
store.checkpoint();
|
|
773
|
+
store.dispatch('set', 1);
|
|
774
|
+
store.checkpoint();
|
|
775
|
+
store.dispatch('set', 2);
|
|
776
|
+
store.checkpoint();
|
|
777
|
+
store.dispatch('set', 3);
|
|
778
|
+
|
|
779
|
+
store.undo(); // -> 2
|
|
780
|
+
expect(store.state.n).toBe(2);
|
|
781
|
+
store.undo(); // -> 1
|
|
782
|
+
expect(store.state.n).toBe(1);
|
|
783
|
+
store.redo(); // -> 2
|
|
784
|
+
expect(store.state.n).toBe(2);
|
|
785
|
+
store.redo(); // -> 3
|
|
786
|
+
expect(store.state.n).toBe(3);
|
|
787
|
+
});
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
|
|
791
|
+
// ===========================================================================
|
|
792
|
+
// Store - reset with no args
|
|
793
|
+
// ===========================================================================
|
|
794
|
+
|
|
795
|
+
describe('Store - reset defaults to initial state', () => {
|
|
796
|
+
it('resets to the original initial state when called with no arguments', () => {
|
|
797
|
+
const store = createStore({
|
|
798
|
+
state: { count: 0, name: 'test' },
|
|
799
|
+
actions: {
|
|
800
|
+
inc(state) { state.count++; },
|
|
801
|
+
rename(state, n) { state.name = n; }
|
|
802
|
+
}
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
store.dispatch('inc');
|
|
806
|
+
store.dispatch('inc');
|
|
807
|
+
store.dispatch('rename', 'changed');
|
|
808
|
+
expect(store.state.count).toBe(2);
|
|
809
|
+
expect(store.state.name).toBe('changed');
|
|
810
|
+
|
|
811
|
+
store.reset();
|
|
812
|
+
expect(store.state.count).toBe(0);
|
|
813
|
+
expect(store.state.name).toBe('test');
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
it('clears undo/redo stacks on reset', () => {
|
|
817
|
+
const store = createStore({
|
|
818
|
+
state: { x: 0 },
|
|
819
|
+
actions: { set(state, v) { state.x = v; } }
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
store.checkpoint();
|
|
823
|
+
store.dispatch('set', 5);
|
|
824
|
+
expect(store.canUndo).toBe(true);
|
|
825
|
+
|
|
826
|
+
store.reset();
|
|
827
|
+
expect(store.canUndo).toBe(false);
|
|
828
|
+
expect(store.canRedo).toBe(false);
|
|
829
|
+
});
|
|
830
|
+
});
|