ccstate-vue 3.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/CHANGELOG.md +10 -0
- package/babel.config.json +3 -0
- package/dist/LICENSE +21 -0
- package/dist/README.md +849 -0
- package/dist/index.cjs +56 -0
- package/dist/index.d.cts +12 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +51 -0
- package/dist/package.json +24 -0
- package/package.json +46 -0
- package/rollup.config.mjs +84 -0
- package/src/__tests__/get-set.test.ts +111 -0
- package/src/__tests__/loadable.test.ts +257 -0
- package/src/__tests__/memory.test.ts +92 -0
- package/src/__tests__/resolved.test.ts +116 -0
- package/src/index.ts +3 -0
- package/src/provider.ts +17 -0
- package/src/useGet.ts +30 -0
- package/src/useLoadable.ts +75 -0
- package/src/useResolved.ts +13 -0
- package/src/useSet.ts +22 -0
- package/tsconfig.json +7 -0
- package/vitest.config.ts +3 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var vue = require('vue');
|
|
4
|
+
var ccstate = require('ccstate');
|
|
5
|
+
|
|
6
|
+
var StoreKey = Symbol('ccstate-vue-store');
|
|
7
|
+
var provideStore = function provideStore(store) {
|
|
8
|
+
vue.provide(StoreKey, store);
|
|
9
|
+
};
|
|
10
|
+
var useStore = function useStore() {
|
|
11
|
+
var store = vue.inject(StoreKey);
|
|
12
|
+
if (store === undefined) {
|
|
13
|
+
throw new Error('Store context not found - did you forget to wrap your app with StoreProvider?');
|
|
14
|
+
}
|
|
15
|
+
return store;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function useGet(atom) {
|
|
19
|
+
var store = useStore();
|
|
20
|
+
var initialValue = store.get(atom);
|
|
21
|
+
var vueState = vue.shallowRef(initialValue);
|
|
22
|
+
var controller = new AbortController();
|
|
23
|
+
store.sub(atom, ccstate.command(function () {
|
|
24
|
+
var nextValue = store.get(atom);
|
|
25
|
+
vueState.value = nextValue;
|
|
26
|
+
}), {
|
|
27
|
+
signal: controller.signal
|
|
28
|
+
});
|
|
29
|
+
if (vue.getCurrentInstance()) {
|
|
30
|
+
vue.onScopeDispose(function () {
|
|
31
|
+
controller.abort();
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return vue.shallowReadonly(vueState);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function useSet(atom) {
|
|
38
|
+
var store = useStore();
|
|
39
|
+
if ('write' in atom) {
|
|
40
|
+
return function () {
|
|
41
|
+
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
42
|
+
args[_key] = arguments[_key];
|
|
43
|
+
}
|
|
44
|
+
var ret = store.set.apply(store, [atom].concat(args));
|
|
45
|
+
return ret;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return function (value) {
|
|
49
|
+
store.set(atom, value);
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
exports.provideStore = provideStore;
|
|
54
|
+
exports.useGet = useGet;
|
|
55
|
+
exports.useSet = useSet;
|
|
56
|
+
exports.useStore = useStore;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Store, Computed, State, Updater, Command } from 'ccstate';
|
|
2
|
+
import { ShallowRef } from 'vue';
|
|
3
|
+
|
|
4
|
+
declare const provideStore: (store: Store) => void;
|
|
5
|
+
declare const useStore: () => Store;
|
|
6
|
+
|
|
7
|
+
declare function useGet<Value>(atom: Computed<Value> | State<Value>): Readonly<ShallowRef<Value>>;
|
|
8
|
+
|
|
9
|
+
declare function useSet<T>(atom: State<T>): (value: T | Updater<T>) => void;
|
|
10
|
+
declare function useSet<T, ARGS extends unknown[]>(atom: Command<T, ARGS>): (...args: ARGS) => T;
|
|
11
|
+
|
|
12
|
+
export { provideStore, useGet, useSet, useStore };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Store, Computed, State, Updater, Command } from 'ccstate';
|
|
2
|
+
import { ShallowRef } from 'vue';
|
|
3
|
+
|
|
4
|
+
declare const provideStore: (store: Store) => void;
|
|
5
|
+
declare const useStore: () => Store;
|
|
6
|
+
|
|
7
|
+
declare function useGet<Value>(atom: Computed<Value> | State<Value>): Readonly<ShallowRef<Value>>;
|
|
8
|
+
|
|
9
|
+
declare function useSet<T>(atom: State<T>): (value: T | Updater<T>) => void;
|
|
10
|
+
declare function useSet<T, ARGS extends unknown[]>(atom: Command<T, ARGS>): (...args: ARGS) => T;
|
|
11
|
+
|
|
12
|
+
export { provideStore, useGet, useSet, useStore };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { provide, inject, shallowRef, getCurrentInstance, onScopeDispose, shallowReadonly } from 'vue';
|
|
2
|
+
import { command } from 'ccstate';
|
|
3
|
+
|
|
4
|
+
var StoreKey = Symbol('ccstate-vue-store');
|
|
5
|
+
var provideStore = function provideStore(store) {
|
|
6
|
+
provide(StoreKey, store);
|
|
7
|
+
};
|
|
8
|
+
var useStore = function useStore() {
|
|
9
|
+
var store = inject(StoreKey);
|
|
10
|
+
if (store === undefined) {
|
|
11
|
+
throw new Error('Store context not found - did you forget to wrap your app with StoreProvider?');
|
|
12
|
+
}
|
|
13
|
+
return store;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function useGet(atom) {
|
|
17
|
+
var store = useStore();
|
|
18
|
+
var initialValue = store.get(atom);
|
|
19
|
+
var vueState = shallowRef(initialValue);
|
|
20
|
+
var controller = new AbortController();
|
|
21
|
+
store.sub(atom, command(function () {
|
|
22
|
+
var nextValue = store.get(atom);
|
|
23
|
+
vueState.value = nextValue;
|
|
24
|
+
}), {
|
|
25
|
+
signal: controller.signal
|
|
26
|
+
});
|
|
27
|
+
if (getCurrentInstance()) {
|
|
28
|
+
onScopeDispose(function () {
|
|
29
|
+
controller.abort();
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return shallowReadonly(vueState);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function useSet(atom) {
|
|
36
|
+
var store = useStore();
|
|
37
|
+
if ('write' in atom) {
|
|
38
|
+
return function () {
|
|
39
|
+
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
40
|
+
args[_key] = arguments[_key];
|
|
41
|
+
}
|
|
42
|
+
var ret = store.set.apply(store, [atom].concat(args));
|
|
43
|
+
return ret;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return function (value) {
|
|
47
|
+
store.set(atom, value);
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export { provideStore, useGet, useSet, useStore };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ccstate-vue",
|
|
3
|
+
"version": "2.2.0",
|
|
4
|
+
"description": "CCState Vue Hooks",
|
|
5
|
+
"private": false,
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/e7h4n/ccstate.git"
|
|
9
|
+
},
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "./index.cjs",
|
|
13
|
+
"module": "./index.js",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"import": "./index.js",
|
|
17
|
+
"require": "./index.cjs"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"vue": ">=3.2.0",
|
|
22
|
+
"ccstate": "workspace:^"
|
|
23
|
+
}
|
|
24
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ccstate-vue",
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "CCState Vue Hooks",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/e7h4n/ccstate.git"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"type": "module",
|
|
11
|
+
"main": "./dist/index.cjs",
|
|
12
|
+
"module": "./dist/index.js",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"import": "./dist/index.js",
|
|
16
|
+
"require": "./dist/index.cjs"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"peerDependencies": {
|
|
20
|
+
"vue": ">=3.2.0",
|
|
21
|
+
"ccstate": "^3.0.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@babel/preset-env": "^7.26.0",
|
|
25
|
+
"@babel/preset-typescript": "^7.26.0",
|
|
26
|
+
"@codspeed/vitest-plugin": "^3.1.1",
|
|
27
|
+
"@rollup/plugin-babel": "^6.0.4",
|
|
28
|
+
"@rollup/plugin-node-resolve": "^15.3.0",
|
|
29
|
+
"@testing-library/jest-dom": "^6.6.3",
|
|
30
|
+
"@testing-library/user-event": "^14.5.2",
|
|
31
|
+
"@testing-library/vue": "^8.1.0",
|
|
32
|
+
"happy-dom": "^15.11.7",
|
|
33
|
+
"jest-leak-detector": "^29.7.0",
|
|
34
|
+
"json": "^11.0.0",
|
|
35
|
+
"rollup": "^4.28.1",
|
|
36
|
+
"rollup-plugin-dts": "^6.1.1",
|
|
37
|
+
"shx": "^0.3.4",
|
|
38
|
+
"signal-timers": "^1.0.4",
|
|
39
|
+
"vitest": "^2.1.8",
|
|
40
|
+
"vue": "^3.5.13"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "rollup -c",
|
|
44
|
+
"prebuild": "shx rm -rf dist"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { babel } from '@rollup/plugin-babel';
|
|
5
|
+
import { dts } from 'rollup-plugin-dts';
|
|
6
|
+
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
|
7
|
+
|
|
8
|
+
const __dirname = path.dirname(new URL(import.meta.url).pathname);
|
|
9
|
+
const projectRootDir = path.resolve(__dirname);
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {string} id
|
|
13
|
+
* @returns {boolean}
|
|
14
|
+
*/
|
|
15
|
+
function external(id) {
|
|
16
|
+
return !id.startsWith('.') && !id.startsWith(projectRootDir);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {{input:string, targetCJS:string, targetES:string}} param0
|
|
21
|
+
* @returns {import('rollup').RollupOptions[]}
|
|
22
|
+
*/
|
|
23
|
+
function generateTarget({ input, targetCJS, targetES }) {
|
|
24
|
+
return [
|
|
25
|
+
{
|
|
26
|
+
input,
|
|
27
|
+
onwarn: (warning) => {
|
|
28
|
+
throw new Error(warning?.message);
|
|
29
|
+
},
|
|
30
|
+
external,
|
|
31
|
+
plugins: [
|
|
32
|
+
nodeResolve({
|
|
33
|
+
extensions: ['.ts'],
|
|
34
|
+
}),
|
|
35
|
+
babel({
|
|
36
|
+
exclude: 'node_modules/**',
|
|
37
|
+
extensions: ['.ts'],
|
|
38
|
+
babelHelpers: 'bundled',
|
|
39
|
+
configFile: path.resolve(projectRootDir, './babel.config.json'),
|
|
40
|
+
}),
|
|
41
|
+
],
|
|
42
|
+
output: [
|
|
43
|
+
{
|
|
44
|
+
file: targetCJS,
|
|
45
|
+
format: 'cjs',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
file: targetES,
|
|
49
|
+
format: 'es',
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
input,
|
|
55
|
+
onwarn: (warning) => {
|
|
56
|
+
throw new Error(warning?.message);
|
|
57
|
+
},
|
|
58
|
+
external,
|
|
59
|
+
plugins: [
|
|
60
|
+
dts({
|
|
61
|
+
respectExternal: true,
|
|
62
|
+
tsconfig: path.resolve(projectRootDir, './tsconfig.json'),
|
|
63
|
+
// https://github.com/Swatinem/rollup-plugin-dts/issues/143
|
|
64
|
+
compilerOptions: { preserveSymlinks: false },
|
|
65
|
+
}),
|
|
66
|
+
],
|
|
67
|
+
output: [
|
|
68
|
+
{
|
|
69
|
+
file: targetCJS.replace(/\.cjs$/, '.d.cts'),
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
file: targetES.replace(/\.js$/, '.d.ts'),
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** @type { Array<import('rollup').RollupOptions> } */
|
|
80
|
+
export default generateTarget({
|
|
81
|
+
input: './src/index.ts',
|
|
82
|
+
targetCJS: './dist/index.cjs',
|
|
83
|
+
targetES: './dist/index.js',
|
|
84
|
+
});
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
import '@testing-library/jest-dom/vitest';
|
|
3
|
+
import { fireEvent, render, cleanup, screen } from '@testing-library/vue';
|
|
4
|
+
import { afterEach, expect, it } from 'vitest';
|
|
5
|
+
import { command, createStore, state } from 'ccstate';
|
|
6
|
+
import { provideStore } from '../provider';
|
|
7
|
+
import { useGet, useSet } from '..';
|
|
8
|
+
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
cleanup();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('increments value on click', async () => {
|
|
14
|
+
const count$ = state(0);
|
|
15
|
+
|
|
16
|
+
const Component = {
|
|
17
|
+
setup() {
|
|
18
|
+
const count = useGet(count$);
|
|
19
|
+
const setCount = useSet(count$);
|
|
20
|
+
return { count, setCount };
|
|
21
|
+
},
|
|
22
|
+
template: `
|
|
23
|
+
<div>
|
|
24
|
+
<p>Times clicked: {{ count }}</p>
|
|
25
|
+
<button @click="setCount((prev) => prev + 1)">increment</button>
|
|
26
|
+
</div>
|
|
27
|
+
`,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
render({
|
|
31
|
+
components: { Component },
|
|
32
|
+
setup() {
|
|
33
|
+
provideStore(createStore());
|
|
34
|
+
},
|
|
35
|
+
template: `<div><Component /></div>`,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
expect(screen.getByText('Times clicked: 0')).toBeInTheDocument();
|
|
39
|
+
|
|
40
|
+
const button = screen.getByText('increment');
|
|
41
|
+
|
|
42
|
+
await fireEvent.click(button);
|
|
43
|
+
await fireEvent.click(button);
|
|
44
|
+
expect(screen.getByText('Times clicked: 2')).toBeInTheDocument();
|
|
45
|
+
|
|
46
|
+
await fireEvent.click(button);
|
|
47
|
+
expect(screen.getByText('Times clicked: 3')).toBeInTheDocument();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('call command by useSet', async () => {
|
|
51
|
+
const count$ = state(0);
|
|
52
|
+
const increase$ = command(({ get, set }, count: number) => {
|
|
53
|
+
set(count$, get(count$) + count);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const Component = {
|
|
57
|
+
setup() {
|
|
58
|
+
const count = useGet(count$);
|
|
59
|
+
const increase = useSet(increase$);
|
|
60
|
+
return { count, increase };
|
|
61
|
+
},
|
|
62
|
+
template: `
|
|
63
|
+
<div>
|
|
64
|
+
<p>Times clicked: {{ count }}</p>
|
|
65
|
+
<button @click="increase(10)">increment</button>
|
|
66
|
+
</div>
|
|
67
|
+
`,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
render({
|
|
71
|
+
components: { Component },
|
|
72
|
+
setup() {
|
|
73
|
+
provideStore(createStore());
|
|
74
|
+
},
|
|
75
|
+
template: `<div><Component /></div>`,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
expect(screen.getByText('Times clicked: 0')).toBeInTheDocument();
|
|
79
|
+
|
|
80
|
+
const button = screen.getByText('increment');
|
|
81
|
+
|
|
82
|
+
await fireEvent.click(button);
|
|
83
|
+
await fireEvent.click(button);
|
|
84
|
+
expect(screen.getByText('Times clicked: 20')).toBeInTheDocument();
|
|
85
|
+
|
|
86
|
+
await fireEvent.click(button);
|
|
87
|
+
expect(screen.getByText('Times clicked: 30')).toBeInTheDocument();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('throw if can not find store', () => {
|
|
91
|
+
const count$ = state(0);
|
|
92
|
+
|
|
93
|
+
const Component = {
|
|
94
|
+
setup() {
|
|
95
|
+
const count = useGet(count$);
|
|
96
|
+
return { count };
|
|
97
|
+
},
|
|
98
|
+
template: `
|
|
99
|
+
<div>
|
|
100
|
+
<p>{{ count }}</p>
|
|
101
|
+
</div>
|
|
102
|
+
`,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
expect(() => {
|
|
106
|
+
render({
|
|
107
|
+
components: { Component },
|
|
108
|
+
template: `<div><Component /></div>`,
|
|
109
|
+
});
|
|
110
|
+
}).toThrow();
|
|
111
|
+
});
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
import '@testing-library/jest-dom/vitest';
|
|
3
|
+
import { cleanup, render, screen } from '@testing-library/vue';
|
|
4
|
+
import { afterEach, expect, it } from 'vitest';
|
|
5
|
+
import { createStore, state } from 'ccstate';
|
|
6
|
+
import { provideStore } from '../provider';
|
|
7
|
+
import { useLoadable, useLastLoadable } from '../useLoadable';
|
|
8
|
+
import { delay } from 'signal-timers';
|
|
9
|
+
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
cleanup();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
function makeDefered<T>(): {
|
|
15
|
+
resolve: (value: T) => void;
|
|
16
|
+
reject: (error: unknown) => void;
|
|
17
|
+
promise: Promise<T>;
|
|
18
|
+
} {
|
|
19
|
+
const deferred: {
|
|
20
|
+
resolve: (value: T) => void;
|
|
21
|
+
reject: (error: unknown) => void;
|
|
22
|
+
promise: Promise<T>;
|
|
23
|
+
} = {} as {
|
|
24
|
+
resolve: (value: T) => void;
|
|
25
|
+
reject: (error: unknown) => void;
|
|
26
|
+
promise: Promise<T>;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
deferred.promise = new Promise((resolve, reject) => {
|
|
30
|
+
deferred.resolve = resolve;
|
|
31
|
+
deferred.reject = reject;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return deferred;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
it('convert promise to loadable', async () => {
|
|
38
|
+
const base = state(Promise.resolve('foo'));
|
|
39
|
+
|
|
40
|
+
const Component = {
|
|
41
|
+
setup() {
|
|
42
|
+
const ret = useLoadable(base);
|
|
43
|
+
return { ret };
|
|
44
|
+
},
|
|
45
|
+
template: `
|
|
46
|
+
<div>
|
|
47
|
+
<p v-if="ret.state === 'loading'">loading</p>
|
|
48
|
+
<p v-else-if="ret.state === 'hasData'">{{ ret.data }}</p>
|
|
49
|
+
</div>
|
|
50
|
+
`,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const store = createStore();
|
|
54
|
+
render({
|
|
55
|
+
components: { Component },
|
|
56
|
+
setup() {
|
|
57
|
+
provideStore(store);
|
|
58
|
+
},
|
|
59
|
+
template: `<div><Component /></div>`,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
expect(screen.getByText('loading')).toBeInTheDocument();
|
|
63
|
+
expect(await screen.findByText('foo')).toBeInTheDocument();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('reset promise atom will reset loadable', async () => {
|
|
67
|
+
const base = state(Promise.resolve('foo'));
|
|
68
|
+
|
|
69
|
+
const Component = {
|
|
70
|
+
setup() {
|
|
71
|
+
const ret = useLoadable(base);
|
|
72
|
+
return { ret };
|
|
73
|
+
},
|
|
74
|
+
template: `
|
|
75
|
+
<div>
|
|
76
|
+
<p v-if="ret.state === 'loading'">loading</p>
|
|
77
|
+
<p v-else-if="ret.state === 'hasData'">{{ ret.data }}</p>
|
|
78
|
+
</div>
|
|
79
|
+
`,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const store = createStore();
|
|
83
|
+
render({
|
|
84
|
+
components: { Component },
|
|
85
|
+
setup() {
|
|
86
|
+
provideStore(store);
|
|
87
|
+
},
|
|
88
|
+
template: `<div><Component /></div>`,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
expect(await screen.findByText('foo')).toBeInTheDocument();
|
|
92
|
+
|
|
93
|
+
const [, promise] = (() => {
|
|
94
|
+
let ret;
|
|
95
|
+
const promise = new Promise((r) => (ret = r));
|
|
96
|
+
return [ret, promise];
|
|
97
|
+
})();
|
|
98
|
+
|
|
99
|
+
store.set(base, promise);
|
|
100
|
+
expect(await screen.findByText('loading')).toBeInTheDocument();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('switchMap', async () => {
|
|
104
|
+
const base = state(Promise.resolve('foo'));
|
|
105
|
+
|
|
106
|
+
const Component = {
|
|
107
|
+
setup() {
|
|
108
|
+
const ret = useLoadable(base);
|
|
109
|
+
return { ret };
|
|
110
|
+
},
|
|
111
|
+
template: `
|
|
112
|
+
<div>
|
|
113
|
+
<p v-if="ret.state === 'loading'">loading</p>
|
|
114
|
+
<p v-else-if="ret.state === 'hasData'">{{ ret.data }}</p>
|
|
115
|
+
</div>
|
|
116
|
+
`,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const store = createStore();
|
|
120
|
+
render({
|
|
121
|
+
components: { Component },
|
|
122
|
+
setup() {
|
|
123
|
+
provideStore(store);
|
|
124
|
+
},
|
|
125
|
+
template: `<div><Component /></div>`,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
expect(await screen.findByText('foo')).toBeInTheDocument();
|
|
129
|
+
|
|
130
|
+
const defered = makeDefered();
|
|
131
|
+
|
|
132
|
+
store.set(base, defered.promise);
|
|
133
|
+
expect(await screen.findByText('loading')).toBeInTheDocument();
|
|
134
|
+
|
|
135
|
+
store.set(base, Promise.resolve('bar'));
|
|
136
|
+
expect(await screen.findByText('bar')).toBeInTheDocument();
|
|
137
|
+
|
|
138
|
+
defered.resolve('baz');
|
|
139
|
+
await delay(0);
|
|
140
|
+
expect(screen.queryByText('baz')).not.toBeInTheDocument();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('switchMap catch error', async () => {
|
|
144
|
+
const base = state(Promise.resolve('foo'));
|
|
145
|
+
|
|
146
|
+
const Component = {
|
|
147
|
+
setup() {
|
|
148
|
+
const ret = useLoadable(base);
|
|
149
|
+
return { ret };
|
|
150
|
+
},
|
|
151
|
+
template: `
|
|
152
|
+
<div>
|
|
153
|
+
<p v-if="ret.state === 'loading'">loading</p>
|
|
154
|
+
<p v-else-if="ret.state === 'hasError'">{{ String(ret.error) }}</p>
|
|
155
|
+
<p v-else-if="ret.state === 'hasData'">{{ ret.data }}</p>
|
|
156
|
+
</div>
|
|
157
|
+
`,
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const store = createStore();
|
|
161
|
+
render({
|
|
162
|
+
components: { Component },
|
|
163
|
+
setup() {
|
|
164
|
+
provideStore(store);
|
|
165
|
+
},
|
|
166
|
+
template: `<div><Component /></div>`,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
expect(await screen.findByText('foo')).toBeInTheDocument();
|
|
170
|
+
|
|
171
|
+
const defered = makeDefered();
|
|
172
|
+
|
|
173
|
+
store.set(base, defered.promise);
|
|
174
|
+
expect(await screen.findByText('loading')).toBeInTheDocument();
|
|
175
|
+
|
|
176
|
+
store.set(base, Promise.resolve('bar'));
|
|
177
|
+
expect(await screen.findByText('bar')).toBeInTheDocument();
|
|
178
|
+
|
|
179
|
+
defered.reject(new Error('error'));
|
|
180
|
+
await delay(0);
|
|
181
|
+
expect(screen.queryByText('Error: error')).not.toBeInTheDocument();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('use lastLoadable should not update when new promise pending', async () => {
|
|
185
|
+
const async$ = state(Promise.resolve(1));
|
|
186
|
+
|
|
187
|
+
const Component = {
|
|
188
|
+
setup() {
|
|
189
|
+
const number = useLastLoadable(async$);
|
|
190
|
+
return { number };
|
|
191
|
+
},
|
|
192
|
+
template: `
|
|
193
|
+
<div>
|
|
194
|
+
<p v-if="number.state === 'loading'">loading</p>
|
|
195
|
+
<p v-else-if="number.state === 'hasData'">num{{ number.data }}</p>
|
|
196
|
+
</div>
|
|
197
|
+
`,
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const store = createStore();
|
|
201
|
+
render({
|
|
202
|
+
components: { Component },
|
|
203
|
+
setup() {
|
|
204
|
+
provideStore(store);
|
|
205
|
+
},
|
|
206
|
+
template: `<div><Component /></div>`,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
expect(await screen.findByText('num1')).toBeInTheDocument();
|
|
210
|
+
|
|
211
|
+
const defered = makeDefered();
|
|
212
|
+
store.set(async$, defered.promise);
|
|
213
|
+
|
|
214
|
+
await delay(0);
|
|
215
|
+
expect(screen.getByText('num1')).toBeInTheDocument();
|
|
216
|
+
defered.resolve(2);
|
|
217
|
+
await delay(0);
|
|
218
|
+
expect(screen.getByText('num2')).toBeInTheDocument();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('use lastLoadable should keep error', async () => {
|
|
222
|
+
const async$ = state(Promise.reject(new Error('error')));
|
|
223
|
+
|
|
224
|
+
const Component = {
|
|
225
|
+
setup() {
|
|
226
|
+
const number = useLastLoadable(async$);
|
|
227
|
+
return { number };
|
|
228
|
+
},
|
|
229
|
+
template: `
|
|
230
|
+
<div>
|
|
231
|
+
<p v-if="number.state === 'loading'">loading</p>
|
|
232
|
+
<p v-else-if="number.state === 'hasError'">{{ String(number.error) }}</p>
|
|
233
|
+
<p v-else-if="number.state === 'hasData'">num{{ number.data }}</p>
|
|
234
|
+
</div>
|
|
235
|
+
`,
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const store = createStore();
|
|
239
|
+
render({
|
|
240
|
+
components: { Component },
|
|
241
|
+
setup() {
|
|
242
|
+
provideStore(store);
|
|
243
|
+
},
|
|
244
|
+
template: `<div><Component /></div>`,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
expect(await screen.findByText('Error: error')).toBeInTheDocument();
|
|
248
|
+
|
|
249
|
+
const defered = makeDefered();
|
|
250
|
+
store.set(async$, defered.promise);
|
|
251
|
+
|
|
252
|
+
await delay(0);
|
|
253
|
+
expect(screen.getByText('Error: error')).toBeInTheDocument();
|
|
254
|
+
defered.resolve(2);
|
|
255
|
+
await delay(0);
|
|
256
|
+
expect(screen.getByText('num2')).toBeInTheDocument();
|
|
257
|
+
});
|