`
378 |
379 | ```js
380 | const enhance = compose(
381 | withClass('someExtraClass'),
382 | withStyle({ width: '400px' }),
383 | );
384 | const C = ehance(componentFromSlot());
385 | ```
386 | ```html
387 |
388 | I will now have someExtraClass and a width of 400px
389 |
390 | ```
391 |
392 | #### createSink
393 | ```js
394 | (
395 | fn: (props: Object) => void,
396 | ) => Component;
397 | ```
398 | Creates a sink component. This component does not render anything, or take any configuration options, but calls `fn` at render time. The `props` parameter contains all `props` and `attrs` passed into the component.
399 |
400 | The createSink method is useful when unit testing your `compose` functions.
401 |
402 | ```js
403 | import { enhance } from '../my-component';
404 |
405 | it('passes a foo prop', (done) => {
406 | const Sink = createSink((props) => {
407 | expect(props.foo).to.equal('bah');
408 | done();
409 | });
410 |
411 | const Enhanced = enhance(Sink);
412 |
413 | mount(Enhanced);
414 | });
415 | ```
416 |
417 | #### renderNothing
418 | ```js
419 | Component
420 | ```
421 | A component that never renders.
422 |
423 | ```js
424 | import { renderNothing } from 'vue-compose';
425 |
426 | export default {
427 | components: { NotFinishedYet: renderNothing },
428 | template: '
',
429 | }
430 | ```
431 | You can also use this in a branch to only render under certain conditions:
432 | ```js
433 | branch(
434 | ({ loading }) => loading,
435 | renderNothing,
436 | )(MyComponent)
437 | ```
438 |
439 | ## FAQ
440 | #### Why is recompose.X missing?
441 | React and Vue look very similar on the surface, but they are actually entirely different beasts. Vue handles a lot more stuff behind the scenes. It's quite easy for React developers to want to shoehorn React techniques into a Vue application, but often there is no need because Vue handles things differently.
442 |
443 | A couple of examples:
444 | - `withPropsOnChange`, `pure`, `onlyUpdateForKeys` - because of it's functional nature, React needs a bit of help deciding whether or not re-render components. This isn't something that Vue suffers with because of its reactive nature, it's already able to work out whether a component is *dirty*.
445 | - `renameProp`, `flattenProp` - React's props are super flexible, meaning you can accept any random assortment of props and then reorganise them before passing them into the next component. Vue requires all props to be defined upfront (so it can watch them), so chances are the props will already be named correctly, and if you want to accept props under different names etc. you have to explicitly add them to the component with `acceptProps`.
446 |
447 | #### Why isn't withHandlers wrapped in a closure?
448 | Recompose's `withHandlers` accepts a function that returns the handler function. This means you have access to the component's props via a closure. Vue is predominantly OOP orientated (see below) so there is no need to wrap props in a closure as you can access them with `this.myProp` or `this.$props.myProp`.
449 |
450 | #### Why are there not more functional HOCs?
451 | Context! Vue is not really written for functional components, and although you can set the `functional` flag on a component, you still don't get a *truly functional* component in the React sense. Your render function still receives a `context` object with properties of the current state of the component. On top of that you lose a lot of Vue's awesome features like computed properties that make memoization totally unecessary. And finally, it is a lot harder to pass non-prop options through multiple functional hocs, because the entire context is not retained.
452 |
453 | #### How can I use this with Vuex?
454 | Easily. You can easily combine **vue-compose** with vuex's helper functions:
455 | ```js
456 | const enhance = compose(
457 | withProps({
458 | ...mapState(['loading']),
459 | ...mapGetters('users', {
460 | name: 'userName'
461 | })
462 | }),
463 | withHandlers(
464 | mapActions('users', [
465 | 'changeName'
466 | ])
467 | ),
468 | );
469 | ```
470 |
--------------------------------------------------------------------------------
/packages/vue-compose/changelog.md:
--------------------------------------------------------------------------------
1 | ## Change Log
2 |
3 | ## 0.3.0
4 | - `createSink` method creates a stub component with a callback. Useful for unit testing.
5 |
6 | ## 0.2.0
7 | - Methods are no longer traditionally curried. They now return a method that accepts a component.
8 | i.e. `withProps({}, c)` must now be called as `withProps({})(c)`.
9 | - If a required prop is provided by `defaultProps({})` or `withProps({})`, the required flag will be disabled from that hoc onwards.
10 |
--------------------------------------------------------------------------------
/packages/vue-compose/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-compose",
3 | "version": "0.0.0",
4 | "description": "Create awesome Vue HOCs",
5 | "main": "dist/vue-compose.js",
6 | "module": "dist/vue-compose.es.js",
7 | "scripts": {
8 | "test": "ava",
9 | "debug": "inspect ../../node_modules/ava/profile",
10 | "coverage": "nyc --reporter=html yarn test",
11 | "lint": "eslint src",
12 | "build": "rm -rf dist && cross-env NODE_ENV=production rollup -c",
13 | "prepublish": "yarn build"
14 | },
15 | "dependencies": {
16 | "vue-hoc": "link:../vue-hoc"
17 | },
18 | "ava": {
19 | "files": [
20 | "spec/**/*.spec.js"
21 | ],
22 | "source": [
23 | "src/**/*.js"
24 | ],
25 | "require": [
26 | "./spec/hooks.js"
27 | ]
28 | },
29 | "repository": {
30 | "type": "git",
31 | "url": "git+https://github.com/jackmellis/vue-hoc.git"
32 | },
33 | "author": "Jack Ellis",
34 | "license": "Apache-2.0",
35 | "bugs": {
36 | "url": "https://github.com/jackmellis/vue-hoc/issues"
37 | },
38 | "homepage": "https://github.com/jackmellis/vue-hoc/blob/master/packages/vue-compose/README.md"
39 | }
40 |
--------------------------------------------------------------------------------
/packages/vue-compose/rollup.config.js:
--------------------------------------------------------------------------------
1 | import babel from 'rollup-plugin-babel';
2 |
3 | export default {
4 | input: 'src/index.js',
5 | output: [
6 | {
7 | file: 'dist/vue-compose.js',
8 | format: 'cjs',
9 | exports: 'named',
10 | },
11 | {
12 | file: 'dist/vue-compose.es.js',
13 | format: 'es',
14 | exports: 'named',
15 | },
16 | ],
17 | plugins: [
18 | babel({
19 | exclude: 'node_modules/**',
20 | }),
21 | ],
22 | external: ['vue-hoc']
23 | };
24 |
--------------------------------------------------------------------------------
/packages/vue-compose/spec/acceptProps.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import {mount} from 'vuenit';
3 | import {acceptProps, mapProps} from 'vue-compose';
4 |
5 | const Component = {
6 | name: 'MyComponent',
7 | props: ['propA', 'propB', 'propC'],
8 | template: `
9 | {{propA}}
10 | {{propB}}
11 | {{propC}}
12 |
`
13 | };
14 | mount(Component);
15 |
16 | test('adds additional props to the component (array)', t => {
17 | const A = acceptProps(['propD'])(Component);
18 | const B = mapProps(({propD}) => {
19 | return {
20 | propA: propD[0],
21 | propB: propD[1],
22 | propC: propD[2],
23 | propD: propD,
24 | };
25 | })(A);
26 | const vm = mount(B, {
27 | props: {
28 | propD: 'ABCDEFG'
29 | }
30 | });
31 |
32 | t.is(vm.$findOne('#a').$text, 'A');
33 | t.is(vm.$findOne('#b').$text, 'B');
34 | t.is(vm.$findOne('#c').$text, 'C');
35 | });
36 |
37 | test('adds additional props to the component (object)', t => {
38 | const A = acceptProps({
39 | propD: String
40 | })(Component);
41 | const B = mapProps(({propD}) => {
42 | return {
43 | propA: propD[0],
44 | propB: propD[1],
45 | propC: propD[2],
46 | propD: propD,
47 | };
48 | })(A);
49 | const vm = mount(B, {
50 | props: {
51 | propD: 'ABCDEFG'
52 | }
53 | });
54 |
55 | t.is(vm.$findOne('#a').$text, 'A');
56 | t.is(vm.$findOne('#b').$text, 'B');
57 | t.is(vm.$findOne('#c').$text, 'C');
58 | });
59 |
60 | test('adds additional props to the component (string)', t => {
61 | const A = acceptProps('propD')(Component);
62 | const B = mapProps(({propD}) => {
63 | return {
64 | propA: propD[0],
65 | propB: propD[1],
66 | propC: propD[2],
67 | propD: propD,
68 | };
69 | })(A);
70 | const vm = mount(B, {
71 | props: {
72 | propD: 'ABCDEFG'
73 | }
74 | });
75 |
76 | t.is(vm.$findOne('#a').$text, 'A');
77 | t.is(vm.$findOne('#b').$text, 'B');
78 | t.is(vm.$findOne('#c').$text, 'C');
79 | });
80 |
--------------------------------------------------------------------------------
/packages/vue-compose/spec/branch.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import sinon from 'sinon';
3 | import { mount } from 'vuenit';
4 |
5 | import { branch } from 'vue-compose';
6 |
7 | const Component = {
8 | props: [ 'flag' ],
9 | template: 'original component
',
10 | };
11 | mount(Component);
12 |
13 | test('renders trueFn when true', t => {
14 | const Wrapper = branch(
15 | (props) => props.flag === true,
16 | (h) => h('div', null, [ 'true' ]),
17 | (h) => h('div', null, [ 'false' ]),
18 | )(Component);
19 |
20 | const vm = mount(Wrapper, {
21 | props: {
22 | flag: true,
23 | },
24 | });
25 | const html = vm.$html;
26 |
27 | t.is(html, 'true
');
28 | });
29 |
30 | test('renders falseFn when false', t => {
31 | const Wrapper = branch(
32 | (props) => props.flag === true,
33 | (h) => h('div', null, [ 'true' ]),
34 | (h) => h('div', null, [ 'false' ]),
35 | )(Component);
36 |
37 | const vm = mount(Wrapper, {
38 | props: {
39 | flag: false,
40 | },
41 | });
42 | const html = vm.$html;
43 |
44 | t.is(html, 'false
');
45 | });
46 |
47 | test('falls back to original component when false', t => {
48 | const Wrapper = branch(
49 | (props) => props.flag === true,
50 | (h) => h('div', null, [ 'true' ]),
51 | )(Component);
52 |
53 | const vm = mount(Wrapper, {
54 | props: {
55 | flag: false,
56 | },
57 | });
58 | const html = vm.$html;
59 |
60 | t.is(html, 'original component
');
61 | });
62 |
63 | test('creates a branch HOC for multiple components', t => {
64 | const enhance = branch(
65 | (props) => props.flag === true,
66 | (h) => h('div', null, [ 'true' ]),
67 | );
68 |
69 | const A = enhance({
70 | props: [ 'flag' ],
71 | render: h => h('div', [ 'A' ]),
72 | });
73 | const B = enhance({
74 | props: [ 'flag' ],
75 | render: h => h('div', [ 'B' ]),
76 | });
77 | const C = enhance({
78 | props: [ 'flag' ],
79 | render: h => h('div', [ 'C' ]),
80 | });
81 |
82 | const a = mount(A, { props: { flag: false }}).$html;
83 | const b = mount(B, { props: { flag: false }}).$html;
84 | const c = mount(C, { props: { flag: false }}).$html;
85 |
86 | t.is(a, 'A
');
87 | t.is(b, 'B
');
88 | t.is(c, 'C
');
89 | });
90 |
--------------------------------------------------------------------------------
/packages/vue-compose/spec/componentFromProp.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import {mount} from 'vuenit';
3 | import {componentFromProp} from 'vue-compose';
4 |
5 | test('creates a component from a prop', t => {
6 | const Button = componentFromProp('component');
7 | const vm = mount(Button, {
8 | props: {
9 | component: 'button',
10 | },
11 | });
12 |
13 | t.is(vm.$el.tagName, 'BUTTON');
14 | });
15 |
16 | test('can be passed another component', t => {
17 | const Button = componentFromProp('component');
18 | const MyComponent = {
19 | name: 'MyComponent',
20 | template: 'my component
',
21 | };
22 | mount(MyComponent);
23 | const vm = mount(Button, {
24 | props: {
25 | component: MyComponent,
26 | },
27 | });
28 |
29 | t.true(vm.$contains(MyComponent));
30 | t.is(vm.$html, 'my component
');
31 | });
32 |
33 | test('allows additional props to be passed into the component', t => {
34 | const Button = componentFromProp('component');
35 | const Anchor = {
36 | name: 'Anchor',
37 | props: ['propA'],
38 | template: 'anchor '
39 | };
40 | mount(Anchor);
41 | const vm = mount(Button, {
42 | props: {
43 | component: Anchor,
44 | href: '/some/url',
45 | propA: 'foo',
46 | },
47 | });
48 |
49 | t.true(vm.$contains(Anchor));
50 | const child = vm.$findOne(Anchor);
51 | t.is(child.propA, 'foo');
52 | t.is(child.$html, 'anchor ');
53 | });
54 |
--------------------------------------------------------------------------------
/packages/vue-compose/spec/componentFromSlot.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import sinon from 'sinon';
3 | import {mount} from 'vuenit';
4 | import {
5 | componentFromSlot,
6 | withStyle,
7 | withClass,
8 | withProps,
9 | withHandlers,
10 | acceptProps,
11 | compose
12 | } from 'vue-compose';
13 |
14 | test('renders the first slot of the component', t => {
15 | const C = componentFromSlot();
16 | const vm = mount(C, {
17 | innerHTML: 'Hello world
'
18 | });
19 |
20 | t.is(vm.$html, 'Hello world
');
21 | });
22 |
23 | test('it can pass additional properties to the slot', t => {
24 | const enhance = compose(
25 | withProps({
26 | width: 50,
27 | }),
28 | withStyle({
29 | height: '400px',
30 | width(){
31 | return `${this.width}%`;
32 | },
33 | }),
34 | withClass('myClass'),
35 | acceptProps(['width']),
36 | );
37 | const C = componentFromSlot();
38 | const enhanced = enhance(C);
39 | const vm = mount(enhanced, {
40 | innerHTML: 'Hello world
'
41 | });
42 |
43 | t.is(vm.$html, 'Hello world
');
44 | });
45 |
46 | test('it can render another component', t => {
47 | const Foo = {
48 | template: ' '
49 | };
50 | mount(Foo);
51 | const Bah = compose(
52 | withProps({
53 | width: 50,
54 | }),
55 | withStyle({
56 | height: '400px',
57 | width(){
58 | return `${this.width}%`;
59 | },
60 | }),
61 | withClass('myClass'),
62 | acceptProps(['width']),
63 | )(componentFromSlot());
64 |
65 | const wrapper = {
66 | components: {Foo, Bah},
67 | template: ' '
68 | };
69 |
70 | const vm = mount(wrapper);
71 |
72 | const actual = vm.$html;
73 | const expected = ' ';
74 |
75 | t.is(actual, expected);
76 | });
77 |
78 | test('it passes props to the inner componet', t => {
79 | const Foo = {
80 | template: '{{value}}
',
81 | props: ['value'],
82 | };
83 | mount(Foo);
84 | const Bah = componentFromSlot();
85 |
86 | const Wrapper = {
87 | components: {Foo, Bah},
88 | template: ' ',
89 | };
90 |
91 | const vm = mount(Wrapper);
92 |
93 | const actual = vm.$html;
94 | const expected = 'some value
';
95 |
96 | t.is(actual, expected);
97 | });
98 |
99 | test('it listens to events of the inner componet', t => {
100 | const spy = sinon.stub().callsFake((x) => {
101 | t.is(x, 'foo');
102 | });
103 | const Inner = {
104 | template: 'I am inner
',
105 | mounted () {
106 | this.$emit('event', 'foo');
107 | },
108 | };
109 | mount(Inner);
110 | const Outer = compose(
111 | withHandlers({
112 | event: spy,
113 | }),
114 | )(componentFromSlot());
115 |
116 | const Wrapper = {
117 | components: { Inner, Outer },
118 | template: ' ',
119 | };
120 |
121 | mount(Wrapper);
122 |
123 | t.is(spy.called, true);
124 | });
125 |
126 | test('it does not overwrite existing listners', t => {
127 | const spy1 = sinon.spy();
128 | const spy2 = sinon.spy();
129 | const Inner = {
130 | template: 'I am inner
',
131 | mounted () {
132 | this.$emit('event', 'foo');
133 | },
134 | };
135 | mount(Inner);
136 | const Outer = compose(
137 | withHandlers({
138 | event: spy2,
139 | }),
140 | )(componentFromSlot());
141 |
142 | const Wrapper = {
143 | components: { Inner, Outer },
144 | template: ' ',
145 | methods: {
146 | event: spy1,
147 | },
148 | };
149 |
150 | mount(Wrapper);
151 |
152 | t.is(spy2.called, true);
153 | t.is(spy1.called, true);
154 | });
155 |
--------------------------------------------------------------------------------
/packages/vue-compose/spec/compose.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import sinon from 'sinon';
3 | import {mount} from 'vuenit';
4 | import {
5 | compose,
6 | defaultProps,
7 | withProps,
8 | withHandlers,
9 | withData,
10 | withClass,
11 | withStyle,
12 | } from 'vue-compose';
13 |
14 | const C = {
15 | props: ['propA', 'propB', 'propC', 'propD'],
16 | name: 'BaseComponent',
17 | template: '
',
18 | };
19 | mount(C);
20 |
21 | test('compose multiple hocs together', t => {
22 | const spy = sinon.spy();
23 | const enhance = compose(
24 | withClass(['classA']),
25 | defaultProps({
26 | propA: 'x',
27 | propB: 'y',
28 | propC: 'z',
29 | }),
30 | withProps({
31 | propA(){
32 | return 'I am prop a';
33 | }
34 | }),
35 | withProps((props) => {
36 | return {
37 | propB: props.propA + ' B',
38 | };
39 | }),
40 | withHandlers({
41 | someEvent: spy,
42 | }),
43 | withClass(['classB']),
44 | withStyle({
45 | width: '100%',
46 | }),
47 | );
48 |
49 | const enhanced = enhance(C);
50 | const wrapper = mount(enhanced, {
51 | props: {
52 | propD: 'from parent',
53 | },
54 | });
55 | const vm = wrapper.$findOne(C);
56 | vm.$emit('someEvent');
57 |
58 | t.is(vm.propA, 'I am prop a');
59 | t.is(vm.propB, 'I am prop a B');
60 | t.is(vm.propC, 'z');
61 | t.is(vm.propD, 'from parent');
62 | t.true(vm.$el.classList.contains('classA'));
63 | t.true(vm.$el.classList.contains('classB'));
64 | t.is(vm.$el.style.width, '100%');
65 |
66 | t.true(spy.called);
67 | });
68 |
--------------------------------------------------------------------------------
/packages/vue-compose/spec/createSink.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import sinon from 'sinon';
3 | import { mount } from 'vuenit';
4 | import {
5 | createSink,
6 | compose,
7 | withProps,
8 | withHandlers,
9 | } from 'vue-compose';
10 |
11 | test('creates a sink component', t => {
12 | const spy = sinon.spy();
13 | const sink = createSink(spy);
14 |
15 | t.false(spy.called);
16 | mount(sink);
17 | t.true(spy.called);
18 | });
19 |
20 | test('sink is called with all attributes and props', t => {
21 | const sink = createSink((props) => {
22 | t.is(props.foo, 'foo');
23 | t.is(props.bah, 'bah');
24 | });
25 |
26 | mount(sink, {
27 | props: {
28 | foo: 'foo',
29 | bah: 'bah',
30 | },
31 | });
32 | });
33 |
34 | test('sink is called with context', t => {
35 | const sink = createSink(function(){
36 | t.is(this.$options.name, 'Sink');
37 | });
38 |
39 | mount(sink);
40 | });
41 |
42 | test('allows easy testing of hocs', t => {
43 | const spy = sinon.spy();
44 | const enhance = compose(
45 | withProps({
46 | foo: 'bah',
47 | }),
48 | withHandlers({
49 | click: spy,
50 | }),
51 | );
52 | const sink = createSink(function (props) {
53 | t.is(props.foo, 'bah');
54 | this.$emit('click');
55 | });
56 |
57 | mount(enhance(sink));
58 |
59 | t.true(spy.called);
60 | });
61 |
--------------------------------------------------------------------------------
/packages/vue-compose/spec/defaultProps.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import sinon from 'sinon';
3 | import {mount} from 'vuenit';
4 | import {defaultProps, withProps, withHooks, compose} from 'vue-compose';
5 |
6 | const Component = {
7 | props: ['propA', 'propB', 'propC'],
8 | template: `
9 | {{propA}}
10 | {{propB}}
11 | {{propC}}
12 |
`
13 | };
14 | mount(Component);
15 |
16 | test('maps undefined props', t => {
17 | const enhance = defaultProps({
18 | propA: 'enhancedPropA',
19 | propC: 'enhancedPropC'
20 | });
21 | const enhanced = enhance(Component);
22 | const vm = mount(enhanced);
23 |
24 | const a = vm.$findOne('#a').$text;
25 | const b = vm.$findOne('#b').$text;
26 | const c = vm.$findOne('#c').$text;
27 |
28 | t.is(a, 'enhancedPropA');
29 | t.is(b, '');
30 | t.is(c, 'enhancedPropC');
31 | });
32 |
33 | test('does not overwrite props', t => {
34 | const enhance = defaultProps({
35 | propA: 'enhancedPropA',
36 | propC: 'enhancedPropC'
37 | });
38 | const enhanced = enhance(Component);
39 | const vm = mount(enhanced, {
40 | props: {
41 | propA: 'A',
42 | propB: 'B',
43 | propC: 'C'
44 | }
45 | });
46 |
47 | const a = vm.$findOne('#a').$text;
48 | const b = vm.$findOne('#b').$text;
49 | const c = vm.$findOne('#c').$text;
50 |
51 | t.is(a, 'A');
52 | t.is(b, 'B');
53 | t.is(c, 'C');
54 | });
55 |
56 | test('prop changes are still mapped', async t => {
57 | const enhance = defaultProps({
58 | propA: 'defaultA',
59 | propB: 'defaultB',
60 | propC: 'defaultC'
61 | });
62 | const enhanced = enhance(Component);
63 | const vm = mount(enhanced, {
64 | props: {
65 | propA: 'A',
66 | propB: 'B',
67 | }
68 | });
69 |
70 | let a = vm.$findOne('#a').$text;
71 | let b = vm.$findOne('#b').$text;
72 | let c = vm.$findOne('#c').$text;
73 |
74 | t.is(a, 'A');
75 | t.is(b, 'B');
76 | t.is(c, 'defaultC');
77 |
78 | vm.propsData.propA = undefined;
79 | vm.propsData.propB = undefined;
80 | vm.propsData.propC = 'c';
81 | await vm.$nextTick();
82 |
83 | a = vm.$findOne('#a').$text;
84 | b = vm.$findOne('#b').$text;
85 | c = vm.$findOne('#c').$text;
86 |
87 | t.is(a, 'defaultA');
88 | t.is(b, 'defaultB');
89 | t.is(c, 'c');
90 | });
91 |
92 | test('can be used in conjunction with defaultProps', t => {
93 | const A = withProps({
94 | propA: 'A'
95 | })(Component);
96 | const B = defaultProps({
97 | propA: 'defaultA',
98 | propB: 'defaultB',
99 | propC: 'defaultC'
100 | })(A);
101 | const vm = mount(B);
102 |
103 | let a = vm.$findOne('#a').$text;
104 | let b = vm.$findOne('#b').$text;
105 | let c = vm.$findOne('#c').$text;
106 |
107 | t.is(a, 'A');
108 | t.is(b, 'defaultB');
109 | t.is(c, 'defaultC');
110 | });
111 |
112 | test('overwrites required prop setting', t => {
113 | const C = Object.assign({}, Component, {
114 | props: {
115 | propA: {
116 | required: true,
117 | },
118 | propB: {
119 | required: true,
120 | },
121 | propC: {},
122 | },
123 | });
124 |
125 | const A = compose(
126 | withHooks({}),
127 | defaultProps({
128 | propA: 'A',
129 | }),
130 | )(C);
131 |
132 | t.is(A.props.propA.required, false);
133 | t.is(A.props.propB.required, true);
134 | });
135 |
--------------------------------------------------------------------------------
/packages/vue-compose/spec/hooks.js:
--------------------------------------------------------------------------------
1 | const hooks = require('require-extension-hooks');
2 | const browserEnv = require('browser-env');
3 | const moduleAlias = require('module-alias');
4 | const path = require('path');
5 | browserEnv();
6 | // moduleAlias.addAlias('vue', 'vue/dist/vue.runtime.min.js');
7 | moduleAlias.addAlias('vue', 'vue/dist/vue.runtime.js');
8 | moduleAlias.addAlias('vue-compose', path.join(__dirname, '../src'));
9 |
10 | hooks('js').exclude('**/node_modules/**/*.*').plugin('babel');
11 |
--------------------------------------------------------------------------------
/packages/vue-compose/spec/mapProps.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import {mount} from 'vuenit';
3 | import {mapProps} from 'vue-compose';
4 |
5 | const Component = {
6 | props: ['propA', 'propB', 'propC'],
7 | template: `
8 | {{propA}}
9 | {{propB}}
10 | {{propC}}
11 |
`
12 | };
13 | mount(Component);
14 |
15 | test('maps props to new values', t => {
16 | const enhance = mapProps(props => ({
17 | propA: 'enhancedPropA',
18 | propB: props.propB,
19 | propC: 'enhancedPropC'
20 | }));
21 | const enhanced = enhance(Component);
22 | const vm = mount(enhanced, {
23 | props: {
24 | propA: 'A',
25 | propB: 'B',
26 | propC: 'C'
27 | }
28 | });
29 |
30 | const a = vm.$findOne('#a').$text;
31 | const b = vm.$findOne('#b').$text;
32 | const c = vm.$findOne('#c').$text;
33 |
34 | t.is(a, 'enhancedPropA');
35 | t.is(b, 'B');
36 | t.is(c, 'enhancedPropC');
37 | });
38 |
39 | test('maps props even if undefined', t => {
40 | const enhance = mapProps(props => ({
41 | propA: 'enhancedPropA',
42 | propB: props.propB,
43 | propC: 'enhancedPropC'
44 | }));
45 | const enhanced = enhance(Component);
46 | const vm = mount(enhanced);
47 |
48 | const a = vm.$findOne('#a').$text;
49 | const b = vm.$findOne('#b').$text;
50 | const c = vm.$findOne('#c').$text;
51 |
52 | t.is(a, 'enhancedPropA');
53 | t.is(b, '');
54 | t.is(c, 'enhancedPropC');
55 | });
56 |
57 | test('does not preserve unampped props', t => {
58 | const enhance = mapProps(() => ({
59 | propA: 'enhancedPropA',
60 | }));
61 | const enhanced = enhance(Component);
62 | const vm = mount(enhanced, {
63 | props: {
64 | propA: 'A',
65 | propB: 'B',
66 | propC: 'C'
67 | }
68 | });
69 |
70 | const a = vm.$findOne('#a').$text;
71 | const b = vm.$findOne('#b').$text;
72 | const c = vm.$findOne('#c').$text;
73 |
74 | t.is(a, 'enhancedPropA');
75 | t.is(b, '');
76 | t.is(c, '');
77 | });
78 |
79 | test('prop changes are still mapped', async t => {
80 | const enhance = mapProps(props => ({
81 | propA: props.propA + '!',
82 | propB: props.propB,
83 | propC: props.propC + '?'
84 | }));
85 | const enhanced = enhance(Component);
86 | const vm = mount(enhanced, {
87 | props: {
88 | propA: 'A',
89 | propB: 'B',
90 | propC: 'C'
91 | }
92 | });
93 |
94 | let a = vm.$findOne('#a').$text;
95 | let b = vm.$findOne('#b').$text;
96 | let c = vm.$findOne('#c').$text;
97 |
98 | t.is(a, 'A!');
99 | t.is(b, 'B');
100 | t.is(c, 'C?');
101 |
102 | vm.propsData.propA = 'a';
103 | vm.propsData.propB = 'b';
104 | vm.propsData.propC = 'c';
105 | await vm.$nextTick();
106 |
107 | a = vm.$findOne('#a').$text;
108 | b = vm.$findOne('#b').$text;
109 | c = vm.$findOne('#c').$text;
110 |
111 | t.is(a, 'a!');
112 | t.is(b, 'b');
113 | t.is(c, 'c?');
114 | });
115 |
--------------------------------------------------------------------------------
/packages/vue-compose/spec/provideInject.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import {
3 | provide,
4 | inject,
5 | createSink,
6 | } from 'vue-compose';
7 | import { mount } from 'vuenit';
8 |
9 | test('provides and injects values', t => {
10 | const Child = inject(['thingy'])(createSink(assertions));
11 | // const Child = {
12 | // inject: ['thingy'],
13 | // render: assertions
14 | // };
15 | const Middle = {
16 | components: { Child },
17 | // template: '
',
18 | render: function anonymous() {
19 | return this._c('div',[this._c('Child')],1);
20 | },
21 | };
22 |
23 | const PureParent = {
24 | // provide: () => ({ thingy: 'floogle' }),
25 | // provide: {
26 | // thingy: 'floogle',
27 | // },
28 | components: { Middle },
29 | // template: '
',
30 | render: function anonymous() {
31 | return this._c('div',[this._c('Middle')],1);
32 | }
33 | };
34 |
35 | const Parent = provide(() => {
36 | return {
37 | thingy: 'floogle',
38 | };
39 | })(PureParent);
40 | // const Parent = PureParent;
41 |
42 | mount(Parent);
43 |
44 | function assertions(){
45 | t.is(this.thingy, 'floogle');
46 | }
47 | });
48 |
--------------------------------------------------------------------------------
/packages/vue-compose/spec/vuex.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import {mount, mockStore} from 'vuenit';
3 | import {
4 | compose,
5 | withProps,
6 | withHandlers,
7 | } from 'vue-compose';
8 | import Vue from 'vue';
9 | import vuex, { mapState, mapActions, mapGetters } from 'vuex';
10 |
11 | Vue.use(vuex);
12 |
13 | const C = {
14 | props: ['name'],
15 | methods: {
16 | handleChange(evt){
17 | this.$emit('changeName', evt.target.value);
18 | },
19 | },
20 | template: '
'
21 | };
22 |
23 | const store = new vuex.Store({
24 | modules: {
25 | users: {
26 | namespaced: true,
27 | state: {
28 | user: {
29 | name: 'jim'
30 | }
31 | },
32 | getters: {
33 | userName(state){
34 | return state.user.name;
35 | }
36 | },
37 | mutations: {
38 | CHANGE_NAME(state, value){
39 | state.user.name = value;
40 | }
41 | },
42 | actions: {
43 | changeName({commit}, value){
44 | commit('CHANGE_NAME', value);
45 | }
46 | }
47 | }
48 | }
49 | });
50 | Vue.prototype.$store = store;
51 |
52 | mount(C);
53 |
54 | const enhance = compose(
55 | withProps(mapGetters('users', {
56 | name: 'userName'
57 | })),
58 | withHandlers(mapActions('users', ['changeName'])),
59 | );
60 |
61 |
62 | test('it works with vuex', t => {
63 | const enhanced = enhance(C);
64 | const vm = mount(enhanced);
65 |
66 | const input = vm.$findOne('input');
67 | t.is(input.value, 'jim');
68 |
69 | input.value = 'bob';
70 | input.$trigger('change');
71 |
72 | t.is(store.state.users.user.name, 'bob');
73 | });
74 |
--------------------------------------------------------------------------------
/packages/vue-compose/spec/withClass.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import {mount} from 'vuenit';
3 | import {withClass} from 'vue-compose';
4 |
5 | const Component = {
6 | props: ['className'],
7 | template: '
'
8 | };
9 | mount(Component);
10 |
11 | test('adds a class to the base component', t => {
12 | const enhance = withClass('classA');
13 | const enhanced = enhance(Component);
14 | const el = mount(enhanced).$el;
15 |
16 | t.true(el.classList.contains('classA'));
17 | });
18 |
19 | test('adds an array of classes to the base component', t => {
20 | const enhanced = withClass(['classA', 'classB'])(Component);
21 | const el = mount(enhanced).$el;
22 |
23 | t.true(el.classList.contains('classA'));
24 | t.true(el.classList.contains('classB'));
25 | });
26 |
27 | test('adds conditional classes to the base component', t => {
28 | const enhanced = withClass({
29 | classA: true,
30 | classB: false,
31 | classC: true,
32 | })(Component);
33 | const el = mount(enhanced).$el;
34 |
35 | t.true(el.classList.contains('classA'));
36 | t.false(el.classList.contains('classB'));
37 | t.true(el.classList.contains('classC'));
38 | });
39 |
40 | test('adds classes from a function', t => {
41 | const enhanced = withClass(function(){
42 | return this.className;
43 | })(Component);
44 | const el = mount(enhanced, {
45 | props: {
46 | className: 'classA'
47 | }
48 | }).$el;
49 | t.true(el.classList.contains('classA'));
50 | });
51 |
52 | test('adds classes from a getter', t => {
53 | const enhanced = withClass({
54 | classA: () => true,
55 | classB: () => false,
56 | classC: () => true,
57 | })(Component);
58 | const el = mount(enhanced).$el;
59 |
60 | t.true(el.classList.contains('classA'));
61 | t.false(el.classList.contains('classB'));
62 | t.true(el.classList.contains('classC'));
63 | });
64 |
65 | test('does not overwrite original classes', t => {
66 | const enhance = withClass('classA');
67 | const enhanced = enhance(Component);
68 | const el = mount(enhanced).$el;
69 |
70 | t.true(el.classList.contains('original'));
71 | });
72 |
73 | test('can be chained to other withClasses', t => {
74 | const enhanced1 = withClass('classA')(Component);
75 | const enhanced2 = withClass('classB')(enhanced1);
76 | const el = mount(enhanced2).$el;
77 |
78 | t.true(el.classList.contains('classA'));
79 | t.true(el.classList.contains('classB'));
80 | t.true(el.classList.contains('original'));
81 | });
82 |
83 | test('passes through parent classes', t => {
84 | const enhanced1 = withClass('classA')(Component);
85 | const enhanced2 = withClass('classB')(enhanced1);
86 | const el = mount({
87 | template: ' ',
88 | components: {
89 | enhanced2
90 | }
91 | }).$el;
92 |
93 | t.true(el.classList.contains('classA'));
94 | t.true(el.classList.contains('classB'));
95 | t.true(el.classList.contains('classC'));
96 | t.true(el.classList.contains('original'));
97 | });
98 |
--------------------------------------------------------------------------------
/packages/vue-compose/spec/withComputed.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import {mount} from 'vuenit';
3 | import {withProps, withComputed, compose} from 'vue-compose';
4 |
5 | const C = {
6 | name: 'BaseComponent',
7 | props: ['propA'],
8 | template: '
'
9 | };
10 | mount(C);
11 |
12 | test('adds computed properties to an existing hoc', t => {
13 | const enhanced = compose(
14 | withComputed({
15 | compA(){
16 | return 'foo';
17 | },
18 | compB(){
19 | return this.compA;
20 | }
21 | }),
22 | withProps({
23 | propA(){
24 | return this.compB;
25 | }
26 | }),
27 | )(C);
28 |
29 | const vm = mount(enhanced);
30 | const child = vm.$findOne(C);
31 |
32 | t.is(child.propA, 'foo');
33 | });
34 |
--------------------------------------------------------------------------------
/packages/vue-compose/spec/withData.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import sinon from 'sinon';
3 | import {mount} from 'vuenit';
4 | import {withData} from 'vue-compose';
5 |
6 | const C = {
7 | name: 'BaseComponent',
8 | props: ['test', 'vest'],
9 | template: '
',
10 | methods: {
11 | updateTest(){
12 | this.$emit('test', 'bah');
13 | },
14 | updateVest(){
15 | this.$emit('updateVest', 'baz');
16 | },
17 | },
18 | };
19 | mount(C);
20 |
21 | test('creates a hoc with a data property', t => {
22 | const enhanced = withData({
23 | test: {
24 | initialValue: 'foo',
25 | }
26 | })(C);
27 | const vm = mount(enhanced);
28 |
29 | t.is(vm.test, 'foo');
30 | });
31 | test('passes the data property to the original component', t => {
32 | const enhanced = withData({
33 | test: {
34 | initialValue: 'foo'
35 | }
36 | })(C);
37 | const vm = mount(enhanced);
38 | const child = vm.$findOne(C);
39 |
40 | t.is(child.test, 'foo');
41 | });
42 | test('passes the data property under another prop name', t => {
43 | const enhanced = withData({
44 | test: {
45 | initialValue: 'foo',
46 | prop: 'vest',
47 | }
48 | })(C);
49 | const vm = mount(enhanced);
50 | const child = vm.$findOne(C);
51 |
52 | t.is(child.test, undefined);
53 | t.is(child.vest, 'foo');
54 | });
55 | test('sets an initial value with a function', t => {
56 | const enhanced = withData({
57 | test: {
58 | initialValue: () => 'foo',
59 | }
60 | })(C);
61 | const vm = mount(enhanced);
62 | const child = vm.$findOne(C);
63 |
64 | t.is(child.test, 'foo');
65 | });
66 | test('adds a listener to the component', t => {
67 | const enhanced = withData({
68 | test: {
69 | initialValue: 'foo',
70 | }
71 | })(C);
72 | const vm = mount(enhanced);
73 | const child = vm.$findOne(C);
74 |
75 | child.updateTest();
76 |
77 | t.is(vm.test, 'bah');
78 | });
79 | test('adds a custom listener name to the component', t => {
80 | const enhanced = withData({
81 | test: {
82 | initialValue: 'foo',
83 | listener: 'updateVest',
84 | }
85 | })(C);
86 | const vm = mount(enhanced);
87 | const child = vm.$findOne(C);
88 |
89 | child.updateVest();
90 |
91 | t.is(vm.test, 'baz');
92 | });
93 | test('calls a custom handler when the listener is emitted', t => {
94 | let spy = sinon.stub().callsFake(function(v){
95 | this.test = v.split('').reverse().join('');
96 | });
97 | const enhanced = withData({
98 | test: {
99 | initialValue: 'foo',
100 | handler: spy,
101 | }
102 | })(C);
103 | const vm = mount(enhanced);
104 | const child = vm.$findOne(C);
105 |
106 | child.updateTest();
107 |
108 | t.true(spy.called);
109 | t.is(vm.test, 'hab');
110 | });
111 |
--------------------------------------------------------------------------------
/packages/vue-compose/spec/withHandlers.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import sinon from 'sinon';
3 | import {mount} from 'vuenit';
4 | import {withHandlers} from 'vue-compose';
5 | const Component = {
6 | name: 'BaseComponent',
7 | template: '
'
8 | };
9 | mount(Component);
10 |
11 | test('adds handlers to the component', t => {
12 | const spy = sinon.spy();
13 | const enhance = withHandlers({
14 | customEvent: spy,
15 | });
16 | const enhanced = enhance(Component);
17 | const vm = mount(enhanced);
18 |
19 | vm.$find('BaseComponent').$emit('customEvent');
20 |
21 | t.true(spy.called);
22 | });
23 |
24 | test('prevents handlers bubbling up', t => {
25 | const spy = sinon.spy();
26 | const enhanced = withHandlers({
27 | customEvent: () => {},
28 | })(Component);
29 | const vm = mount({
30 | template: ' ',
31 | methods: {
32 | triggerSpy: spy,
33 | },
34 | components: {enhanced},
35 | });
36 |
37 | vm.$find('BaseComponent').$emit('customEvent');
38 |
39 | t.false(spy.called);
40 | });
41 |
42 | test('can bubble events up', t => {
43 | const spy1 = sinon.spy();
44 | const spy2 = sinon.stub().callsFake(function () {
45 | this.$emit('customEvent');
46 | });
47 | const enhanced = withHandlers({
48 | customEvent: spy2,
49 | })(Component);
50 | const vm = mount({
51 | template: ' ',
52 | methods: {
53 | triggerSpy: spy1,
54 | },
55 | components: {enhanced},
56 | });
57 |
58 | vm.$find('BaseComponent').$emit('customEvent');
59 |
60 | t.true(spy1.called);
61 | t.true(spy2.called);
62 | });
63 |
64 | test('handlers can call other handlers', t => {
65 | const spy = sinon.spy();
66 |
67 | const enhanced = withHandlers({
68 | first: spy,
69 | second(){
70 | this.handleFirst();
71 | }
72 | })(Component);
73 |
74 | const vm = mount({
75 | template: ' ',
76 | components: {enhanced},
77 | });
78 |
79 | vm.$find('BaseComponent').$emit('second');
80 |
81 | t.true(spy.called);
82 | });
83 |
--------------------------------------------------------------------------------
/packages/vue-compose/spec/withHooks.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import sinon from 'sinon';
3 | import {mount} from 'vuenit';
4 | import {withHooks} from 'vue-compose';
5 |
6 | const Component = {
7 | template: '
'
8 | };
9 | mount(Component);
10 |
11 | test('adds a created hook', t => {
12 | const spy = sinon.spy();
13 | const enhanced = withHooks({
14 | created: spy
15 | })(Component);
16 | mount(enhanced);
17 |
18 | t.truthy(enhanced.created);
19 | t.true(spy.called);
20 | });
21 |
22 | test('ignores non-hooks', t => {
23 | const enhanced = withHooks({
24 | foo : 'bah',
25 | })(Component);
26 | t.is(enhanced.foo, undefined);
27 | });
28 |
--------------------------------------------------------------------------------
/packages/vue-compose/spec/withMethods.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import {mount} from 'vuenit';
3 | import {withProps, withMethods, compose} from 'vue-compose';
4 |
5 | const C = {
6 | name: 'BaseComponent',
7 | props: ['propA'],
8 | template: '
'
9 | };
10 | mount(C);
11 |
12 | test('adds methods to an existing hoc', t => {
13 | debugger; //eslint-disable-line
14 | const enhanced = compose(
15 | withMethods({
16 | compA(){
17 | return 'foo';
18 | },
19 | compB(){
20 | return this.compA();
21 | }
22 | }),
23 | withProps({
24 | propA(){
25 | return this.compB();
26 | }
27 | }),
28 | )(C);
29 |
30 | const vm = mount(enhanced);
31 | const child = vm.$findOne(C);
32 |
33 | t.is(child.propA, 'foo');
34 | });
35 |
--------------------------------------------------------------------------------
/packages/vue-compose/spec/withNativeHandlers.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import sinon from 'sinon';
3 | import { mount } from 'vuenit';
4 | import { withNativeHandlers } from 'vue-compose';
5 | const Component = {
6 | name: 'BaseComponent',
7 | template: 'Click me '
8 | };
9 | mount(Component);
10 |
11 | test('adds handlers to the component', t => {
12 | const spy = sinon.spy();
13 | const enhance = withNativeHandlers({
14 | click: spy,
15 | });
16 | const enhanced = enhance(Component);
17 | const vm = mount(enhanced);
18 |
19 | vm.$find('button').$trigger('click');
20 |
21 | t.true(spy.called);
22 | });
23 |
--------------------------------------------------------------------------------
/packages/vue-compose/spec/withPassive.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import sinon from 'sinon';
3 | import {mount} from 'vuenit';
4 | import {withPassive} from 'vue-compose';
5 | const Component = {
6 | name: 'BaseComponent',
7 | template: '
'
8 | };
9 | mount(Component);
10 |
11 | test('adds handlers to the component', t => {
12 | const spy = sinon.spy();
13 | const enhance = withPassive({
14 | customEvent: spy,
15 | });
16 | const enhanced = enhance(Component);
17 | const vm = mount(enhanced);
18 |
19 | vm.$find('BaseComponent').$emit('customEvent');
20 |
21 | t.true(spy.called);
22 | });
23 |
24 | test('does not prevent events bubbling up', t => {
25 | const spy = sinon.spy();
26 | const enhanced = withPassive({
27 | customEvent: () => {},
28 | })(Component);
29 | const vm = mount({
30 | template: ' ',
31 | methods: {
32 | triggerSpy: spy,
33 | },
34 | components: {enhanced},
35 | });
36 |
37 | vm.$find('BaseComponent').$emit('customEvent');
38 |
39 | t.true(spy.called);
40 | });
41 |
42 | test('handlers can call other handlers', t => {
43 | const spy = sinon.spy();
44 |
45 | const enhanced = withPassive({
46 | first: spy,
47 | second(){
48 | this.handleFirst();
49 | }
50 | })(Component);
51 |
52 | const vm = mount({
53 | template: ' ',
54 | components: {enhanced},
55 | });
56 |
57 | vm.$find('BaseComponent').$emit('second');
58 |
59 | t.true(spy.called);
60 | });
61 |
--------------------------------------------------------------------------------
/packages/vue-compose/spec/withProps.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import {mount} from 'vuenit';
3 | import {withProps} from 'vue-compose';
4 |
5 | const Component = {
6 | props: ['propA', 'propB', 'propC'],
7 | template: `
8 | {{propA}}
9 | {{propB}}
10 | {{propC}}
11 |
`
12 | };
13 | mount(Component);
14 |
15 | test('maps props to new values (object)', t => {
16 | const enhance = withProps({
17 | propA: 'enhancedPropA',
18 | propC: 'enhancedPropC'
19 | });
20 | const enhanced = enhance(Component);
21 | const vm = mount(enhanced, {
22 | props: {
23 | propA: 'A',
24 | propB: 'B',
25 | propC: 'C'
26 | }
27 | });
28 |
29 | const a = vm.$findOne('#a').$text;
30 | const b = vm.$findOne('#b').$text;
31 | const c = vm.$findOne('#c').$text;
32 |
33 | t.is(a, 'enhancedPropA');
34 | t.is(b, 'B');
35 | t.is(c, 'enhancedPropC');
36 | });
37 |
38 | test('maps props to new values (function)', t => {
39 | const enhance = withProps(props => ({
40 | propA: 'enhancedPropA',
41 | propB: props.propB,
42 | propC: 'enhancedPropC'
43 | }));
44 | const enhanced = enhance(Component);
45 | const vm = mount(enhanced, {
46 | props: {
47 | propA: 'A',
48 | propB: 'B',
49 | propC: 'C'
50 | }
51 | });
52 |
53 | const a = vm.$findOne('#a').$text;
54 | const b = vm.$findOne('#b').$text;
55 | const c = vm.$findOne('#c').$text;
56 |
57 | t.is(a, 'enhancedPropA');
58 | t.is(b, 'B');
59 | t.is(c, 'enhancedPropC');
60 | });
61 |
62 | test('maps props even if undefined', t => {
63 | const enhance = withProps(props => ({
64 | propA: 'enhancedPropA',
65 | propB: props.propB,
66 | propC: 'enhancedPropC'
67 | }));
68 | const enhanced = enhance(Component);
69 | const vm = mount(enhanced);
70 |
71 | const a = vm.$findOne('#a').$text;
72 | const b = vm.$findOne('#b').$text;
73 | const c = vm.$findOne('#c').$text;
74 |
75 | t.is(a, 'enhancedPropA');
76 | t.is(b, '');
77 | t.is(c, 'enhancedPropC');
78 | });
79 |
80 | test('preserves unampped props (function)', t => {
81 | const enhance = withProps(() => ({
82 | propA: 'enhancedPropA',
83 | }));
84 | const enhanced = enhance(Component);
85 | const vm = mount(enhanced, {
86 | props: {
87 | propA: 'A',
88 | propB: 'B',
89 | propC: 'C'
90 | }
91 | });
92 |
93 | const a = vm.$findOne('#a').$text;
94 | const b = vm.$findOne('#b').$text;
95 | const c = vm.$findOne('#c').$text;
96 |
97 | t.is(a, 'enhancedPropA');
98 | t.is(b, 'B');
99 | t.is(c, 'C');
100 | });
101 |
102 | test('preserves unampped props (object)', t => {
103 | const enhance = withProps({
104 | propA: 'enhancedPropA'
105 | });
106 | const enhanced = enhance(Component);
107 | const vm = mount(enhanced, {
108 | props: {
109 | propA: 'A',
110 | propB: 'B',
111 | propC: 'C'
112 | }
113 | });
114 |
115 | const a = vm.$findOne('#a').$text;
116 | const b = vm.$findOne('#b').$text;
117 | const c = vm.$findOne('#c').$text;
118 |
119 | t.is(a, 'enhancedPropA');
120 | t.is(b, 'B');
121 | t.is(c, 'C');
122 | });
123 |
124 | test('prop changes are still mapped', async t => {
125 | const enhance = withProps(props => ({
126 | propA: props.propA + '!',
127 | propB: props.propB,
128 | propC: props.propC + '?'
129 | }));
130 | const enhanced = enhance(Component);
131 | const vm = mount(enhanced, {
132 | props: {
133 | propA: 'A',
134 | propB: 'B',
135 | propC: 'C'
136 | }
137 | });
138 |
139 | let a = vm.$findOne('#a').$text;
140 | let b = vm.$findOne('#b').$text;
141 | let c = vm.$findOne('#c').$text;
142 |
143 | t.is(a, 'A!');
144 | t.is(b, 'B');
145 | t.is(c, 'C?');
146 |
147 | vm.propsData.propA = 'a';
148 | vm.propsData.propB = 'b';
149 | vm.propsData.propC = 'c';
150 | await vm.$nextTick();
151 |
152 | a = vm.$findOne('#a').$text;
153 | b = vm.$findOne('#b').$text;
154 | c = vm.$findOne('#c').$text;
155 |
156 | t.is(a, 'a!');
157 | t.is(b, 'b');
158 | t.is(c, 'c?');
159 | });
160 |
161 | test('overwrites required prop setting', t => {
162 | const C = Object.assign({}, Component, {
163 | props: {
164 | propA: {
165 | required: true,
166 | },
167 | propB: {
168 | required: true,
169 | },
170 | propC: {
171 | required: true,
172 | },
173 | },
174 | });
175 |
176 | const A = withProps({
177 | propA: () => 'A',
178 | propB: () => undefined,
179 | })(C);
180 |
181 | t.is(A.props.propA.required, false);
182 | t.is(A.props.propB.required, false);
183 | t.is(A.props.propC.required, true);
184 | });
185 |
--------------------------------------------------------------------------------
/packages/vue-compose/spec/withStyle.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import {mount} from 'vuenit';
3 | import {withStyle, withProps, compose} from 'vue-compose';
4 |
5 | const Component = {
6 | props: ['className'],
7 | template: '
'
8 | };
9 | mount(Component);
10 |
11 | test('adds a style to the base component', t => {
12 | const enhance = withStyle('color:red;');
13 | const enhanced = enhance(Component);
14 | const el = mount(enhanced).$el;
15 |
16 | t.is(el.style.color, 'red');
17 | });
18 |
19 | test('adds conditional styles to the base component', t => {
20 | const enhanced = withStyle({
21 | width: '50%',
22 | color: false,
23 | backgroundColor: 'blue',
24 | })(Component);
25 | const el = mount(enhanced).$el;
26 |
27 | t.is(el.style.width, '50%');
28 | t.is(el.style.color, '');
29 | t.is(el.style.backgroundColor, 'blue');
30 | });
31 |
32 | test('adds styles from a function', t => {
33 | const enhanced = withStyle(function(){
34 | return this.className;
35 | })(Component);
36 | const el = mount(enhanced, {
37 | props: {
38 | className: {
39 | width: '50%',
40 | },
41 | },
42 | }).$el;
43 | t.is(el.style.width, '50%');
44 | });
45 |
46 | test('adds styles from a getter', t => {
47 | const enhanced = withStyle({
48 | color: () => 'red',
49 | })(Component);
50 | const el = mount(enhanced).$el;
51 |
52 | t.is(el.style.color, 'red');
53 | });
54 |
55 | test('does not overwrite original styles', t => {
56 | const enhance = withStyle('color:red');
57 | const enhanced = enhance(Component);
58 | const el = mount(enhanced).$el;
59 |
60 | t.is(el.style.width, '100%');
61 | });
62 |
63 | test('can be chained to other withClasses', t => {
64 | const enhanced1 = withStyle({color:'red'})(Component);
65 | const enhanced2 = withStyle({backgroundColor:'blue'})(enhanced1);
66 | const el = mount(enhanced2).$el;
67 |
68 | t.is(el.style.color, 'red');
69 | t.is(el.style.backgroundColor, 'blue');
70 | t.is(el.style.width, '100%');
71 | });
72 |
73 | test('can be chained with other hocs', t => {
74 | const enhanced = compose(
75 | withStyle({
76 | width: '100%'
77 | }),
78 | withStyle(() => ({
79 | height: '400px'
80 | })),
81 | withProps({
82 | foo: 'bah'
83 | }),
84 | withStyle({
85 | color: () => 'green'
86 | }),
87 | )(Component);
88 | const el = mount(enhanced).$el;
89 |
90 | t.is(el.style.color, 'green');
91 | t.is(el.style.height, '400px');
92 | t.is(el.style.width, '100%');
93 | });
94 |
95 | test('passes through parent classes', t => {
96 | const enhanced1 = withStyle('color:red')(Component);
97 | const enhanced2 = withStyle('backgroundColor:blue')(enhanced1);
98 | const el = mount({
99 | template: ' ',
100 | components: {
101 | enhanced2
102 | }
103 | }).$el;
104 |
105 | t.is(el.style.color, 'red');
106 | t.is(el.style.backgroundColor, 'blue');
107 | t.is(el.style.opacity, '0.5');
108 | t.is(el.style.width, '100%');
109 | });
110 |
--------------------------------------------------------------------------------
/packages/vue-compose/src/hocs/acceptProps.js:
--------------------------------------------------------------------------------
1 | import { createHOC } from 'vue-hoc';
2 | import { wrapName } from '../mutators/setName';
3 |
4 | export default (props) => {
5 | if (typeof props === 'string'){
6 | props = [props];
7 | }
8 | return (ctor) => createHOC(ctor, {
9 | props,
10 | name: wrapName('acceptProps')(ctor),
11 | });
12 | };
13 |
--------------------------------------------------------------------------------
/packages/vue-compose/src/hocs/branch.js:
--------------------------------------------------------------------------------
1 | import { createHOC, createRenderFn } from 'vue-hoc';
2 | import { wrapName } from '../mutators/setName';
3 |
4 | export default (testFn, trueFn, f) => (ctor) => {
5 | let falseFn = f;
6 | if (falseFn == null) {
7 | falseFn = createRenderFn(ctor);
8 | }
9 | if (typeof falseFn === 'object' && typeof falseFn.render === 'function') {
10 | falseFn = falseFn.render;
11 | }
12 | if (typeof trueFn === 'object' && typeof trueFn.render === 'function') {
13 | trueFn = trueFn.render;
14 | }
15 |
16 | return createHOC(ctor, {
17 | name: wrapName('branch')(ctor),
18 | render (h) {
19 | if (testFn.call(this, this.$props)) {
20 | return trueFn.call(this, h);
21 | } else {
22 | return falseFn.call(this, h);
23 | }
24 | },
25 | });
26 | };
27 |
--------------------------------------------------------------------------------
/packages/vue-compose/src/hocs/defaultProps.js:
--------------------------------------------------------------------------------
1 | import assign from '../utils/assign';
2 | import mapProps from './mapProps';
3 | import { wrapName } from '../mutators/setName';
4 |
5 | const defaultProps = (defaults) => (ctor) => {
6 | const keys = Object.keys(defaults);
7 |
8 | const hoc = mapProps((props) => {
9 | const result = assign({}, props);
10 | keys.forEach(key => {
11 | if (result[key] === undefined){
12 | result[key] = defaults[key];
13 | }
14 | });
15 | return result;
16 | })(ctor);
17 | hoc.name = wrapName('defaultProps')(ctor);
18 | keys.forEach((key) => {
19 | if (hoc.props[key] && hoc.props[key].required) {
20 | hoc.props[key] = assign(hoc.props[key], { required: false });
21 | }
22 | });
23 | return hoc;
24 | };
25 |
26 | export default defaultProps;
27 |
--------------------------------------------------------------------------------
/packages/vue-compose/src/hocs/mapProps.js:
--------------------------------------------------------------------------------
1 | import { createHOC } from 'vue-hoc';
2 | import { wrapName } from '../mutators/setName';
3 |
4 | export default (mapper) => {
5 | const options = {
6 | computed: {
7 | mapProps(){
8 | return mapper.call(this, this.$props);
9 | }
10 | },
11 | };
12 | const renderWith = {
13 | props(){
14 | return this.mapProps;
15 | }
16 | };
17 | return (ctor) => {
18 | const hoc = createHOC(ctor, options, renderWith);
19 | hoc.name = wrapName('mapProps')(hoc);
20 | return hoc;
21 | };
22 | };
23 |
--------------------------------------------------------------------------------
/packages/vue-compose/src/hocs/provide.js:
--------------------------------------------------------------------------------
1 | import { createHOC } from 'vue-hoc';
2 | import { wrapName } from '../mutators/setName';
3 |
4 | const provide = (provide) => (ctor) => {
5 | const hoc = createHOC(ctor, {
6 | provide,
7 | });
8 | hoc.name = wrapName('provide')(ctor);
9 | return hoc;
10 | };
11 |
12 | export default provide;
13 |
--------------------------------------------------------------------------------
/packages/vue-compose/src/hocs/withClass.js:
--------------------------------------------------------------------------------
1 | import { createHOC } from 'vue-hoc';
2 | import { wrapName } from '../mutators/setName';
3 |
4 | export default (classes) => (ctor) => {
5 | return createHOC(ctor, {
6 | name: wrapName('withClass')(ctor),
7 | }, {
8 | class: classes,
9 | });
10 | };
11 |
--------------------------------------------------------------------------------
/packages/vue-compose/src/hocs/withData.js:
--------------------------------------------------------------------------------
1 | import { createHOC } from 'vue-hoc';
2 | import assign from '../utils/assign';
3 | import { wrapName } from '../mutators/setName';
4 |
5 | const withData = (data) => {
6 | const keys = Object.keys(data);
7 | const listeners = {};
8 |
9 | const dataCreator = function () {
10 | const result = {};
11 | keys.forEach(key => {
12 | const config = data[key];
13 | const { initialValue } = config;
14 | if (Object.hasOwnProperty.call(config, 'initialValue')){
15 | if (typeof initialValue === 'function'){
16 | result[key] = initialValue.call(this, this.$props);
17 | }else{
18 | result[key] = initialValue;
19 | }
20 | }else{
21 | result[key] = undefined;
22 | }
23 | });
24 | return result;
25 | };
26 |
27 | const propsCreator = function(ownerProps) {
28 | const result = assign({}, ownerProps);
29 | keys.forEach(key => {
30 | const propName = data[key].prop || key;
31 | result[propName] = this[key];
32 | });
33 | return result;
34 | };
35 |
36 | keys.forEach(key => {
37 | const listenerName = data[key].listener || key;
38 |
39 | listeners[listenerName] = data[key].handler || function(value){
40 | this[key] = value;
41 | };
42 | });
43 |
44 | return (ctor) => {
45 | const hoc = createHOC(ctor, {
46 | data: dataCreator,
47 | name: wrapName('withData')(ctor),
48 | }, {
49 | listeners,
50 | props: propsCreator,
51 | });
52 |
53 | keys.forEach(key => {
54 | if (hoc.props[key]){
55 | delete hoc.props[key];
56 | }
57 | });
58 |
59 | return hoc;
60 | };
61 | };
62 |
63 | export default withData;
64 |
--------------------------------------------------------------------------------
/packages/vue-compose/src/hocs/withHandlers.js:
--------------------------------------------------------------------------------
1 | import { createHOC } from 'vue-hoc';
2 | import { wrapName } from '../mutators/setName';
3 |
4 | const withHandlers = (handlers) => {
5 | const listeners = {};
6 | const methods = {};
7 |
8 | Object.keys(handlers).forEach(key => {
9 | const methodName = `handle${key.charAt(0).toUpperCase()}${key.substr(1)}`;
10 | methods[methodName] = handlers[key];
11 | listeners[key] = function () {
12 | return this[methodName].apply(this, arguments);
13 | };
14 | });
15 |
16 | return (ctor) => createHOC(ctor, {
17 | name: wrapName('withHandlers')(ctor),
18 | methods,
19 | }, {
20 | listeners,
21 | });
22 | };
23 |
24 | export default withHandlers;
25 |
--------------------------------------------------------------------------------
/packages/vue-compose/src/hocs/withHooks.js:
--------------------------------------------------------------------------------
1 | import { createHOC } from 'vue-hoc';
2 | import { wrapName } from '../mutators/setName';
3 |
4 | const withHooks = (hooks) => (ctor) => {
5 | const definiteHooks = {
6 | name: wrapName('withHooks')(ctor),
7 | };
8 | Object.keys(hooks).forEach(key => {
9 | const value = hooks[key];
10 | if (typeof value === 'function'){
11 | definiteHooks[key] = value;
12 | }
13 | });
14 | return createHOC(ctor, definiteHooks);
15 | };
16 |
17 | export default withHooks;
18 |
--------------------------------------------------------------------------------
/packages/vue-compose/src/hocs/withNativeHandlers.js:
--------------------------------------------------------------------------------
1 | import { createHOC } from 'vue-hoc';
2 | import { wrapName } from '../mutators/setName';
3 |
4 | const withNativeHandlers = (handlers) => {
5 | const listeners = {};
6 | const methods = {};
7 |
8 | Object.keys(handlers).forEach(key => {
9 | const methodName = `handle${key.charAt(0).toUpperCase()}${key.substr(1)}`;
10 | methods[methodName] = handlers[key];
11 | listeners[key] = function () {
12 | return this[methodName].apply(this, arguments);
13 | };
14 | });
15 |
16 | return (ctor) => createHOC(ctor, {
17 | name: wrapName('withNativeHandlers')(ctor),
18 | methods,
19 | }, {
20 | nativeOn: listeners,
21 | });
22 | };
23 |
24 | export default withNativeHandlers;
25 |
--------------------------------------------------------------------------------
/packages/vue-compose/src/hocs/withPassive.js:
--------------------------------------------------------------------------------
1 | import withHandlers from './withHandlers';
2 | import { wrapName } from '../mutators/setName';
3 |
4 | const withPassive = (passives) => {
5 | const handlers = {};
6 | Object.keys(passives).forEach(key => {
7 | handlers[key] = function (...args) {
8 | passives[key].apply(this, args);
9 | this.$emit.apply(this, [key].concat(args));
10 | };
11 | });
12 |
13 | return (ctor) => {
14 | const hoc = withHandlers(handlers)(ctor);
15 | hoc.name = wrapName('withPassive')(ctor);
16 |
17 | return hoc;
18 | };
19 | };
20 |
21 | export default withPassive;
22 |
--------------------------------------------------------------------------------
/packages/vue-compose/src/hocs/withProps.js:
--------------------------------------------------------------------------------
1 | import assign from '../utils/assign';
2 | import { createHOC } from 'vue-hoc';
3 | import mapProps from './mapProps';
4 | import { wrapName } from '../mutators/setName';
5 |
6 | const withPropsFn = (ctor, mapper) => {
7 | return mapProps(function (props) {
8 | const mapped = mapper.call(this, props);
9 | return assign({}, props, mapped);
10 | })(ctor);
11 | };
12 |
13 | const withComputedProps = (ctor, keys, allProps) => {
14 | const computed = {};
15 | const props = {};
16 | keys.forEach(key => {
17 | let value = allProps[key];
18 | if (typeof value === 'function'){
19 | const computedName = `computed_${key}`;
20 | computed[computedName] = value;
21 | value = function(){
22 | return this[computedName];
23 | };
24 | }
25 | props[key] = value;
26 | });
27 | const hoc = createHOC(ctor, { computed }, { props });
28 | keys.forEach((key) => {
29 | if (hoc.props[key] && hoc.props[key].required) {
30 | hoc.props[key] = assign(hoc.props[key], { required: false });
31 | }
32 | });
33 | return hoc;
34 | };
35 |
36 | const withStaticProps = (ctor, props) => {
37 | const hoc = createHOC(ctor, null, { props });
38 | Object.keys(props).forEach((key) => {
39 | if (hoc.props[key] && hoc.props[key].required) {
40 | hoc.props[key] = assign(hoc.props[key], { required: false });
41 | }
42 | });
43 | return hoc;
44 | };
45 |
46 | const getHoc = (mapper, ctor) => {
47 | if (typeof mapper === 'function'){
48 | return withPropsFn(ctor, mapper);
49 | }
50 | const keys = Object.keys(mapper);
51 | if (keys.some(key => typeof mapper[key] === 'function')){
52 | return withComputedProps(ctor, keys, mapper);
53 | }
54 | return withStaticProps(ctor, mapper);
55 | };
56 |
57 | const withProps = (mapper) => (ctor) => {
58 | const hoc = getHoc(mapper, ctor);
59 | hoc.name = wrapName('withProps')(ctor);
60 | return hoc;
61 | };
62 |
63 | export default withProps;
64 |
--------------------------------------------------------------------------------
/packages/vue-compose/src/hocs/withStyle.js:
--------------------------------------------------------------------------------
1 | import { createHOC } from 'vue-hoc';
2 | import { wrapName } from '../mutators/setName';
3 |
4 | const withStyle = (styles) => (ctor) => {
5 | return createHOC(ctor, {
6 | name: wrapName('withStyle')(ctor),
7 | }, {
8 | style: styles,
9 | });
10 | };
11 |
12 | export default withStyle;
13 |
--------------------------------------------------------------------------------
/packages/vue-compose/src/index.js:
--------------------------------------------------------------------------------
1 | import branch from './hocs/branch';
2 | import mapProps from './hocs/mapProps';
3 | import withProps from './hocs/withProps';
4 | import defaultProps from './hocs/defaultProps';
5 | import acceptProps from './hocs/acceptProps';
6 | import withHandlers from './hocs/withHandlers';
7 | import withNativeHandlers from './hocs/withNativeHandlers';
8 | import withPassive from './hocs/withPassive';
9 | import withHooks from './hocs/withHooks';
10 | import withClass from './hocs/withClass';
11 | import withStyle from './hocs/withStyle';
12 | import withData from './hocs/withData';
13 | import provide from './hocs/provide';
14 |
15 | import withComputed from './mutators/withComputed';
16 | import withMethods from './mutators/withMethods';
17 | import setName from './mutators/setName';
18 | import inject from './mutators/inject';
19 |
20 | import createSink from './utils/createSink';
21 | import componentFromProp from './utils/componentFromProp';
22 | import componentFromSlot from './utils/componentFromSlot';
23 | import renderNothing from './utils/renderNothing';
24 | import { compose, pipe } from './utils/compose';
25 |
26 | export {
27 | branch,
28 | mapProps,
29 | withProps,
30 | defaultProps,
31 | acceptProps,
32 | withHandlers,
33 | withNativeHandlers,
34 | withPassive,
35 | withHooks,
36 | withClass,
37 | withStyle,
38 | withData,
39 | provide,
40 |
41 | withComputed,
42 | withMethods,
43 | setName,
44 | inject,
45 |
46 | createSink,
47 | componentFromProp,
48 | componentFromSlot,
49 | renderNothing,
50 | compose,
51 | pipe,
52 | };
53 |
54 | export default {
55 | mapProps,
56 | withProps,
57 | defaultProps,
58 | acceptProps,
59 | withHandlers,
60 | withNativeHandlers,
61 | withPassive,
62 | withHooks,
63 | withClass,
64 | withStyle,
65 | withData,
66 |
67 | withComputed,
68 | withMethods,
69 | setName,
70 |
71 | createSink,
72 | componentFromProp,
73 | componentFromSlot,
74 | compose,
75 | pipe,
76 | };
77 |
--------------------------------------------------------------------------------
/packages/vue-compose/src/mutators/inject.js:
--------------------------------------------------------------------------------
1 | const inject = (inject) => (ctor) => {
2 | const target = typeof ctor === 'function' ?
3 | ctor.options :
4 | ctor;
5 |
6 | target.mixins = (target.mixins || []).concat({
7 | inject,
8 | });
9 |
10 | return target;
11 | };
12 |
13 | export default inject;
14 |
--------------------------------------------------------------------------------
/packages/vue-compose/src/mutators/setName.js:
--------------------------------------------------------------------------------
1 | const setName = (name) => (ctor) => {
2 | const target = typeof ctor === 'function' ?
3 | ctor.options :
4 | ctor;
5 |
6 | target.name = name;
7 |
8 | return ctor;
9 | };
10 |
11 | export const getName = (ctor) => {
12 | const target = typeof ctor === 'function' ?
13 | ctor.options :
14 | ctor;
15 |
16 | return target.name || '';
17 | };
18 |
19 | export const wrapName = (name) => (ctor) => {
20 | const cname = getName(ctor) || 'Anonymous';
21 | return `${name}-${cname}`;
22 | };
23 |
24 | export default setName;
25 |
--------------------------------------------------------------------------------
/packages/vue-compose/src/mutators/withComputed.js:
--------------------------------------------------------------------------------
1 | import getMixins from '../utils/getMixins';
2 |
3 | const withComputed = (computed) => (ctor) => {
4 | const mixins = getMixins(ctor);
5 | mixins.push({
6 | computed,
7 | });
8 | return ctor;
9 | };
10 |
11 | export default withComputed;
12 |
--------------------------------------------------------------------------------
/packages/vue-compose/src/mutators/withMethods.js:
--------------------------------------------------------------------------------
1 | import getMixins from '../utils/getMixins';
2 |
3 | const withMethods = (methods) => (ctor) => {
4 | const mixins = getMixins(ctor);
5 | mixins.push({
6 | methods,
7 | });
8 | return ctor;
9 | };
10 |
11 | export default withMethods;
12 |
--------------------------------------------------------------------------------
/packages/vue-compose/src/utils/assign.js:
--------------------------------------------------------------------------------
1 | // IE9-11 do not support `Object.assign`
2 | const poly = function (target, ...sources) {
3 | if (target == null) {
4 | throw new TypeError('Uncaught TypeError: Cannot convert undefined or null to object');
5 | }
6 |
7 | for (let i = 0, il = sources.length; i < il; i += 1) {
8 | const source = sources[i];
9 | if (source == null) {
10 | continue;
11 | }
12 |
13 | for (let key in source) {
14 | if (Object.hasOwnProperty.call(source, key)) {
15 | Object.defineProperty(target, key, {
16 | enumerable: true,
17 | writable: true,
18 | value: source[key],
19 | });
20 | }
21 | }
22 | }
23 |
24 | // $FlowFixMe
25 | return target;
26 | };
27 |
28 | const assign = Object.assign || poly;
29 |
30 | export default assign;
31 |
--------------------------------------------------------------------------------
/packages/vue-compose/src/utils/componentFromProp.js:
--------------------------------------------------------------------------------
1 | import { createRenderFn } from 'vue-hoc';
2 |
3 | const componentFromProp = (propName) => {
4 | return {
5 | name: 'ComponentFromProp',
6 | props: {
7 | [propName]: {
8 | type: [String, Object, Function],
9 | required: true
10 | }
11 | },
12 | render(h){
13 | const C = this[propName];
14 | return createRenderFn(C).call(this, h);
15 | }
16 | };
17 | };
18 |
19 | export default componentFromProp;
20 |
--------------------------------------------------------------------------------
/packages/vue-compose/src/utils/componentFromSlot.js:
--------------------------------------------------------------------------------
1 | import assign from '../utils/assign';
2 |
3 | const componentFromSlot = (options = {}) => {
4 | return assign({
5 | name: 'ComponentFromSlot',
6 | render(){
7 | const props = assign({}, this.$attrs, this.$props );
8 | const listeners = this.$listeners || {};
9 | let vNode;
10 |
11 | if (this.$scopedSlots.children) {
12 | vNode = this.$scopedSlots.children(props);
13 | }
14 | if (this.$scopedSlots.default) {
15 | vNode = this.$scopedSlots.default(props)[0];
16 | }
17 | if (this.$slots.default) {
18 | const slot = this.$slots.default[0];
19 | const options = slot.componentOptions || {};
20 | const propsData = options.propsData = options.propsData || {};
21 | Object.assign(propsData, props);
22 | vNode = slot;
23 | }
24 |
25 | if (!vNode) {
26 | throw new Error('No slot content for ComponentFromSlot component');
27 | }
28 |
29 | const options = vNode.componentOptions || {};
30 | const listenerData = options.listeners = options.listeners || {};
31 | Object.keys(listeners).forEach((key) => {
32 | if (listenerData[key] == null) {
33 | listenerData[key] = listeners[key];
34 | } else if (Array.isArray(listenerData[key])) {
35 | listenerData[key].push(listeners[key]);
36 | } else {
37 | listenerData[key] = [ listenerData[key], listeners[key] ];
38 | }
39 | });
40 |
41 | return vNode;
42 | }
43 | }, options);
44 | };
45 |
46 | export default componentFromSlot;
47 |
--------------------------------------------------------------------------------
/packages/vue-compose/src/utils/compose.js:
--------------------------------------------------------------------------------
1 | export const pipe = (...args) => (Component) => args.reduce((result, fn) => fn(result), Component);
2 |
3 | export const compose = (...args) => pipe.apply(null, args.reverse());
4 |
--------------------------------------------------------------------------------
/packages/vue-compose/src/utils/createSink.js:
--------------------------------------------------------------------------------
1 | const createSink = (fn) => {
2 | return {
3 | name: 'Sink',
4 | render(){
5 | fn.call(this, this.$attrs);
6 | },
7 | };
8 | };
9 |
10 | export default createSink;
11 |
--------------------------------------------------------------------------------
/packages/vue-compose/src/utils/getMixins.js:
--------------------------------------------------------------------------------
1 | export default (ctor) => {
2 | let mixins;
3 | if (ctor.options){
4 | if (!ctor.options.mixins){
5 | ctor.options.mixins = mixins = [];
6 | }else{
7 | mixins = ctor.options.mixins;
8 | }
9 | }else{
10 | if (!ctor.mixins){
11 | ctor.mixins = mixins = [];
12 | }else{
13 | mixins = ctor.mixins;
14 | }
15 | }
16 | return mixins;
17 | };
18 |
--------------------------------------------------------------------------------
/packages/vue-compose/src/utils/renderNothing.js:
--------------------------------------------------------------------------------
1 | import createSink from './createSink';
2 |
3 | const RenderNothing = createSink(() => {});
4 |
5 | export default RenderNothing;
6 |
--------------------------------------------------------------------------------
/packages/vue-hoc/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "production": {
4 | "presets" : [
5 | ["env", {
6 | "targets" : {
7 | "browsers" : ["last 2 versions"],
8 | "node" : "current",
9 | },
10 | "modules": false
11 | }]
12 | ],
13 | "sourceMaps" : true,
14 | "plugins": ["external-helpers"],
15 | },
16 | "test": {
17 | "presets" : [
18 | ["env", {
19 | "targets" : {
20 | "browsers" : ["last 2 versions"],
21 | "node" : "current",
22 | },
23 | "modules": "commonjs"
24 | }]
25 | ],
26 | "sourceMaps" : true,
27 | "plugins": []
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/packages/vue-hoc/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "parser": "babel-eslint",
3 | "plugins": [],
4 | "env": {
5 | "browser": true,
6 | "es6": true
7 | },
8 | "extends": [
9 | "eslint:recommended",
10 | "plugin:import/errors",
11 | "plugin:import/warnings",
12 | ],
13 | "parserOptions": {
14 | "sourceType": "module"
15 | },
16 | "rules": {
17 | "indent": [
18 | "error",
19 | 2
20 | ],
21 | "linebreak-style": [
22 | "error",
23 | "unix"
24 | ],
25 | "quotes": [
26 | "error",
27 | "single"
28 | ],
29 | "semi": [
30 | "error",
31 | "always"
32 | ]
33 | }
34 | };
35 |
--------------------------------------------------------------------------------
/packages/vue-hoc/.npmignore:
--------------------------------------------------------------------------------
1 | src
2 | spec
3 |
4 | # Logs
5 | logs
6 | *.log
7 | npm-debug.log*
8 | yarn-debug.log*
9 | yarn-error.log*
10 |
11 | # Runtime data
12 | pids
13 | *.pid
14 | *.seed
15 | *.pid.lock
16 |
17 | # Directory for instrumented libs generated by jscoverage/JSCover
18 | lib-cov
19 |
20 | # Coverage directory used by tools like istanbul
21 | coverage
22 |
23 | # nyc test coverage
24 | .nyc_output
25 |
26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
27 | .grunt
28 |
29 | # Bower dependency directory (https://bower.io/)
30 | bower_components
31 |
32 | # node-waf configuration
33 | .lock-wscript
34 |
35 | # Compiled binary addons (http://nodejs.org/api/addons.html)
36 | build/Release
37 |
38 | # Dependency directories
39 | node_modules/
40 | jspm_packages/
41 |
42 | # Typescript v1 declaration files
43 | typings/
44 |
45 | # Optional npm cache directory
46 | .npm
47 |
48 | # Optional eslint cache
49 | .eslintcache
50 |
51 | # Optional REPL history
52 | .node_repl_history
53 |
54 | # Output of 'npm pack'
55 | *.tgz
56 |
57 | # Yarn Integrity file
58 | .yarn-integrity
59 |
60 | # dotenv environment variables file
61 | .env
62 |
--------------------------------------------------------------------------------
/packages/vue-hoc/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 | ## [0.4.6](https://github.com/jackmellis/vue-hoc/compare/0.3.0...0.4.6) (2018-10-16)
3 |
4 |
5 | ### Bug Fixes
6 |
7 | * **vue-hoc:** correctly render named slots ([561f52e](https://github.com/jackmellis/vue-hoc/commit/561f52e)), closes [#36](https://github.com/jackmellis/vue-hoc/issues/36) [#30](https://github.com/jackmellis/vue-hoc/issues/30)
8 | * **vue-hoc:** missing build ([a3ccc8c](https://github.com/jackmellis/vue-hoc/commit/a3ccc8c))
9 |
10 |
11 |
12 |
13 | ## 0.4.4 (2018-06-21 14:56:21 +0100)
14 |
15 |
16 | ### Bug Fixes
17 |
18 | * **vue-hoc:** mixin props included ([93fe1a5](https://github.com/jackmellis/vue-hoc/commit/93fe1a5)), closes [#25](https://github.com/jackmellis/vue-hoc/issues/25)
19 | * **vue-hoc:** mixin props should be overridden by component props ([7fa9e44](https://github.com/jackmellis/vue-hoc/commit/7fa9e44)), closes [#28](https://github.com/jackmellis/vue-hoc/issues/28)
20 |
21 |
22 |
23 |
24 | ## 0.4.2 (2018-04-20 12:31:31 +0100)
25 |
26 |
27 | ### Bug Fixes
28 |
29 | * **vue-hoc:** Remove condition whether node is text ([071dd06](https://github.com/jackmellis/vue-hoc/commit/071dd06))
30 | * **vue-hoc:** rendering a html element component failed to render child text nodes ([01728ef](https://github.com/jackmellis/vue-hoc/commit/01728ef)), closes [#18](https://github.com/jackmellis/vue-hoc/issues/18)
31 |
32 |
33 |
34 |
35 | ## 0.4.1 (2018-04-11 06:19:52 +0100)
36 |
37 |
38 | ### Bug Fixes
39 |
40 | * **vue-hoc:** fix readme ([29f584e](https://github.com/jackmellis/vue-hoc/commit/29f584e))
41 |
42 |
43 | ### Features
44 |
45 | * **vue-hoc:** move to mono repo ([883d356](https://github.com/jackmellis/vue-hoc/commit/883d356))
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/packages/vue-hoc/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/packages/vue-hoc/README.md:
--------------------------------------------------------------------------------
1 | # vue-hoc
2 | Create Higher Order Vue Components
3 |
4 | Inspired by https://github.com/vuejs/vue/issues/6201
5 |
6 | Sister projects: [vue-compose](https://www.npmjs.com/package/vue-compose) and [vuex-compose](https://www.npmjs.com/package/vuex-compose)
7 |
8 | ## Installation
9 | ```
10 | npm install --save vue-hoc
11 | ```
12 |
13 | ## Usage
14 | The simplest way to create a Higher Order Component is with the `createHOC` method. It takes a base component, a set of component options to apply to the HOC, and a set of *data* properties to pass to the component during render.
15 | ```js
16 | import { createHOC } from 'vue-hoc';
17 | import MyComponent from '../my-component';
18 |
19 | const options = {
20 | name: 'MyEnhancedComponent',
21 | computed: {
22 | myComputedProperty(){
23 | return this.someProp + ' computed';
24 | }
25 | },
26 | created(){
27 | console.log('Created')
28 | }
29 | };
30 |
31 | const renderWith = {
32 | props: {
33 | someProp(){
34 | return this.myComputedProperty;
35 | }
36 | },
37 | listeners: {
38 | someEvent(arg){
39 | this.$emit('someOtherEvent', arg);
40 | }
41 | }
42 | };
43 |
44 | const enhanced = createHOC(MyComponent, options, renderWith);
45 | ```
46 | The resulting HOC component will render the base component, but will pass in the value of `myComputedProperty` in place of `someProp`.
47 |
48 | The alt method `createHOCc` exposes a curried version of the same method, where the component is the last argument, allowing you to write *HOC creators* and potentially chain up multiple hocs:
49 | ```js
50 | import { createHOCc } from 'vue-hoc';
51 | import { compose } from 'ramda';
52 | import MyComponent from '../my-component';
53 |
54 | const withCreatedHook = createHOCc({
55 | created(){
56 | console.log('Created');
57 | }
58 | }, null);
59 |
60 | const withAmendedProp = createHOCc(null, {
61 | props: {
62 | someProp(){
63 | return this.someProp + ' amended';
64 | }
65 | }
66 | });
67 |
68 | // we can now create a HOC using these methods
69 | const MyComponent2 = withCreatedHook(MyComponent);
70 |
71 | // and we can do multiple hocs:
72 | const MyComponent3 = withAmendedProp(withCreatedHook(MyComponent));
73 |
74 | // and with a composer like ramda's compose, we can make it more readable:
75 | const enhance = compose(
76 | withAmendedProp,
77 | withCreatedHook
78 | );
79 | const MyComponent4 = enhance(MyComponent);
80 | ```
81 |
82 | ## API
83 | ### createHOC
84 | ```js
85 | (Component: Object | Function, options?: Object, renderWith?: Object) => Object;
86 | ```
87 | Wraps a component in a higher order component. Any props, listeners, and attributes will be passed through the HOC into the original Component.
88 | ```js
89 | const hocComponent = createHOC(Component);
90 | ```
91 |
92 | #### options
93 | The options object will be used as the HOC's component definition. Here you can pass any valid [component definition options](https://vuejs.org/v2/api/#Options-Data).
94 | ```js
95 | const withCreatedHook = createHOC(Component, {
96 | created(){
97 | console.log(this.someProp);
98 | // Where some prop is a prop defined on the original component.
99 | // The HOC will have access to it and it will still be passed on to the original component.
100 | }
101 | });
102 | ```
103 | **vue-hoc** will automatically inherit the base component's props so you can access these from within the hoc and they will be passed into the base component during the render. If you set a new value for props, it will be merged with the inherited props using Vue's [option merging strategies](https://vuejs.org/v2/api/#optionMergeStrategies).
104 | ```js
105 | createHOC(Component, {
106 | props: ['someAdditionalProp']
107 | });
108 | ```
109 |
110 | **vue-hoc** will also automatically create a render function for the HOC, but you can override this by setting a `render` function yourself. Keep in mind, however, that a custom render function will no longer handle the `renderWith` options.
111 | ```js
112 | createHOC(Component, {
113 | render(h){
114 | /* ... */
115 | }
116 | });
117 | ```
118 |
119 | #### renderWith
120 | The renderWith object allows you to amend what props, listeners and attributes will be passed into the child component. In actuality, you can pass in any property that is accepted by Vue's [createElement](https://vuejs.org/v2/guide/render-function.html#The-Data-Object-In-Depth) method.
121 |
122 | >The exception is that the `on` property is renamed to `listeners`.
123 |
124 | Each option can be one of the following formats:
125 | ```js
126 | {
127 | [name: string]: any
128 | }
129 | ```
130 | This will just pass static properties into the component instance. i.e.
131 | ```js
132 | createHOC(Component, null, {
133 | props: {
134 | staticProp: 'foo',
135 | otherStaticProp: [1, 2, 3]
136 | }
137 | });
138 | ```
139 | The properties will be merged into the existing properties.
140 |
141 | ```js
142 | {
143 | [name: string]: (owner: Object) => any
144 | }
145 | ```
146 | This allows you to calculate specific properties individually. You can also include static properties alongisde this. i.e.
147 | ```js
148 | createHOC(Component, null, {
149 | props: {
150 | dynamicProp(props){
151 | return props.someProp + ' dynamic';
152 | },
153 | otherDynamicProp(){
154 | return this.someOtherProp + ' dynamic';
155 | },
156 | staticProp: 'foo'
157 | }
158 | });
159 | ```
160 | The properties will be merged into the existing properties.
161 |
162 | Keep in mind that `listeners`, `nativeOn`, and `scopedSlots` are meant to be functions so they will not be evaluated.
163 |
164 | ```js
165 | (owner: Object) => any
166 | ```
167 | This allows to return the entire property object. i.e.
168 | ```js
169 | createHOC(Component, null, {
170 | props(props){
171 | return {
172 | ...props,
173 | dynamicProp: 'dynamic'
174 | };
175 | }
176 | });
177 | ```
178 | Unlike the previous variants, this will *not* automatically merge with the existing properties.
179 |
180 | ### createHOCc
181 | ```js
182 | (options: Object, renderWith?: Object) => (Component: Object | Function) => Object;
183 | ```
184 | This is a curried variation of the `createHOC` method. This allows you to build a HOC creator and pass in a component at the end.
185 |
186 | ### createRenderFn
187 | ```js
188 | (Component: Object, renderWith?: Object)=> Function;
189 | ```
190 | createRenderFn is responsible for rendering the wrapped component in your hoc.
191 | ```js
192 | const hoc = createHOC(Component, {
193 | render: createRenderFn(Component, {})
194 | });
195 | ```
196 | It is already used by `createHOC` to generate the render property of the component so you do not need to pass it in every time.
197 |
198 | #### options
199 | See [renderWith](#renderwith).
200 |
201 | ### createRenderFnc
202 | ```js
203 | (options: Object) => (Component: Object)=> Function;
204 | ```
205 | A curried version of `createRenderFn`.
206 |
207 | ### normalizeSlots
208 | ```js
209 | (slots: Object) => Array;
210 | ```
211 | A simple method that takes a component's slots and converts them into an array. This is used to pass distributed content from a parent to a child component during the render.
212 |
--------------------------------------------------------------------------------
/packages/vue-hoc/changelog.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 | ## 0.3.2
3 | - rendering a html element component (i.e. calling `h('button')`) failed to render child text nodes
4 |
5 | ## 0.3.0
6 | - unknown render props are passed to the component as attributes
7 |
8 | ## 0.2.1
9 | - allow slots to contain both elements and text nodes together
10 |
11 | ## 0.2.0
12 | - `createHOCc` and `createRenderFnc` are now not *fully* curried methods, each takes configuration arguments and returns a function that accepts a component.
13 | ```js
14 | // 0.1:
15 | createHOCc(options)(renderWith)(Component)
16 | // or
17 | createHOCc(options, renderWith, Component)
18 | // must now be written as
19 | createHOCc(options, renderWith)(Component)
20 | ```
21 |
22 | ## 0.1.6
23 | - Correctly handle scoped (template) slots [10](https://github.com/jackmellis/vue-hoc/pull/10)
24 |
--------------------------------------------------------------------------------
/packages/vue-hoc/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-hoc",
3 | "version": "0.0.0",
4 | "description": "Create Higher Order Vue Components",
5 | "main": "dist/vue-hoc.js",
6 | "module": "dist/vue-hoc.es.js",
7 | "scripts": {
8 | "test": "ava",
9 | "coverage": "nyc --reporter=html yarn test",
10 | "debug": "inspect ../../node_modules/ava/profile",
11 | "build": "rm -rf dist && cross-env NODE_ENV=production rollup -c",
12 | "lint": "eslint src",
13 | "prepublish": "yarn build"
14 | },
15 | "ava": {
16 | "files": [
17 | "spec/**/*.spec.js"
18 | ],
19 | "source": [
20 | "src/**/*.js"
21 | ],
22 | "require": [
23 | "./spec/hooks.js"
24 | ],
25 | "concurrency": 8
26 | },
27 | "repository": {
28 | "type": "git",
29 | "url": "git+https://github.com/jackmellis/vue-hoc.git"
30 | },
31 | "bugs": {
32 | "url": "https://github.com/jackmellis/vue-hoc/issues"
33 | },
34 | "homepage": "https://github.com/jackmellis/vue-hoc/blob/master/packages/vue-hoc/README.md",
35 | "author": "Jack Ellis",
36 | "license": "Apache-2.0",
37 | "peerDependencies": {
38 | "vue": "^2.4.0"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/packages/vue-hoc/rollup.config.js:
--------------------------------------------------------------------------------
1 | import babel from 'rollup-plugin-babel';
2 |
3 | export default {
4 | input: 'src/index.js',
5 | output: [
6 | {
7 | file: 'dist/vue-hoc.js',
8 | format: 'cjs',
9 | exports: 'named',
10 | },
11 | {
12 | file: 'dist/vue-hoc.es.js',
13 | format: 'es',
14 | exports: 'named',
15 | },
16 | ],
17 | plugins: [
18 | babel({
19 | exclude: 'node_modules/**',
20 | }),
21 | ],
22 | external: ['vue']
23 | };
24 |
--------------------------------------------------------------------------------
/packages/vue-hoc/spec/createHOC.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import sinon from 'sinon';
3 | import {mount} from 'vuenit';
4 | import {createHOC, createHOCc, createRenderFn} from '../src';
5 |
6 | const Component = {
7 | props : ['propA'],
8 | template : '{{propA}}
',
9 | };
10 | mount(Component);
11 |
12 | test('wraps a component in a hoc', t => {
13 | const hoc = createHOC(Component);
14 | const vm = mount(hoc, {
15 | props: {
16 | propA: 'foo'
17 | }
18 | });
19 |
20 | t.true(vm.$html.includes('foo'));
21 | });
22 |
23 | test('wraps a component in a curried hoc', t => {
24 | const hoc = createHOCc(null, null)(Component);
25 | const vm = mount(hoc, {
26 | props: {
27 | propA: 'foo'
28 | }
29 | });
30 |
31 | t.true(vm.$html.includes('foo'));
32 | });
33 |
34 | test('has a default name', t => {
35 | const hoc = createHOC(Component);
36 | const vm = mount(hoc, {
37 | props: {
38 | propA: 'foo'
39 | }
40 | });
41 |
42 | t.is(vm.$name, 'AnonymousHOC');
43 | });
44 |
45 | test('extends the compnent name', t => {
46 | const hoc = createHOC(Object.assign({name:'MyComponent'}, Component));
47 | const vm = mount(hoc, {
48 | name: 'MyComponent',
49 | props: {
50 | propA: 'foo'
51 | }
52 | });
53 |
54 | t.is(vm.$name, 'MyComponentHOC');
55 | });
56 |
57 | test('extends the compnent name', t => {
58 | const hoc = createHOC(Component, {
59 | name: 'MyHoc',
60 | });
61 | const vm = mount(hoc, {
62 | name: 'MyComponent',
63 | props: {
64 | propA: 'foo'
65 | }
66 | });
67 |
68 | t.is(vm.$name, 'MyHoc');
69 | });
70 |
71 | test('provide props to the hoc', t => {
72 | const hoc = createHOC(Component, null, {
73 | props: {
74 | propA: 'from hoc'
75 | }
76 | });
77 | const vm = mount(hoc, {
78 | props: {
79 | propA: 'foo'
80 | }
81 | });
82 |
83 | t.true(vm.$html.includes('from hoc'));
84 | t.false(vm.$html.includes('foo'));
85 | });
86 |
--------------------------------------------------------------------------------
/packages/vue-hoc/spec/events.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import sinon from 'sinon';
3 | import {mount} from 'vuenit';
4 | import {createHOC, createHOCc, createRenderFn} from '../src';
5 |
6 | const Component = {
7 | template : `
8 | 1
9 |
`,
10 | methods : {
11 | handleClick(){
12 | this.$emit('button1click');
13 | }
14 | }
15 | };
16 | mount(Component);
17 |
18 | test('it emits an event', t => {
19 | const spy = sinon.spy();
20 | const vm = mount(Component, {
21 | on : {
22 | button1click : spy
23 | }
24 | });
25 | vm.$findOne('#button1').$trigger('click');
26 |
27 | t.true(spy.called);
28 | });
29 |
30 | test('it emits an event through a hoc', t => {
31 | const spy = sinon.spy();
32 | const hoc = createHOC(Component);
33 | const C2 = {
34 | components : {hoc},
35 | template : ' ',
36 | methods: {
37 | callSpy(){
38 | spy();
39 | }
40 | }
41 | };
42 | const vm = mount(C2);
43 |
44 | vm.$findOne('#button1').$trigger('click');
45 |
46 | t.true(spy.called);
47 | });
48 |
49 | test('it can intercept an event', t => {
50 | const spy1 = sinon.spy(), spy2 = sinon.spy();
51 | const hoc = createHOCc(null, {
52 | listeners: {
53 | button1click: spy2,
54 | }
55 | })(Component);
56 | const C2 = {
57 | components : {hoc},
58 | template : ' ',
59 | methods: {
60 | callSpy(){
61 | spy1();
62 | }
63 | }
64 | };
65 | const vm = mount(C2);
66 |
67 | vm.$findOne('#button1').$trigger('click');
68 |
69 | t.false(spy1.called);
70 | t.true(spy2.called);
71 | });
72 |
73 | test('it can bubble an event (function syntax)', t => {
74 | const spy1 = sinon.spy(), spy2 = sinon.spy();
75 | const hoc = createHOC(Component, {
76 | render : createRenderFn(Component, {
77 | listeners(){
78 | return {
79 | button1click : () => {
80 | spy2();
81 | this.$emit('button1click');
82 | }
83 | };
84 | }
85 | })
86 | });
87 | const C2 = {
88 | components : {hoc},
89 | template : ' ',
90 | methods: {
91 | callSpy(){
92 | spy1();
93 | }
94 | }
95 | };
96 | const vm = mount(C2);
97 | t.log(vm.$html);
98 | vm.$findOne('#button1').$trigger('click');
99 |
100 | t.true(spy1.called);
101 | t.true(spy2.called);
102 | });
103 |
104 | test('it can bubble an event (object syntax)', t => {
105 | const spy1 = sinon.spy(), spy2 = sinon.spy();
106 | const hoc = createHOC(Component, {
107 | render : createRenderFn(Component, {
108 | listeners: {
109 | button1click(){
110 | spy2();
111 | this.$emit('button1click');
112 | }
113 | }
114 | })
115 | });
116 | const C2 = {
117 | components : {hoc},
118 | template : ' ',
119 | methods: {
120 | callSpy(){
121 | spy1();
122 | }
123 | }
124 | };
125 | const vm = mount(C2);
126 |
127 | vm.$findOne('#button1').$trigger('click');
128 |
129 | t.true(spy1.called);
130 | t.true(spy2.called);
131 | });
132 |
133 | test('events bubble through multiple hocs', t => {
134 | const spy = sinon.spy();
135 | const hoc1 = createHOCc({}, null)(Component);
136 | const hoc2 = createHOCc({}, null)(hoc1);
137 | const hoc3 = createHOCc({}, null)(hoc2);
138 | const C2 = {
139 | components : {hoc3},
140 | template : ' ',
141 | methods: {
142 | callSpy(){
143 | spy();
144 | }
145 | }
146 | };
147 | const vm = mount(C2);
148 |
149 | vm.$findOne('#button1').$trigger('click');
150 |
151 | t.true(spy.called);
152 | });
153 |
--------------------------------------------------------------------------------
/packages/vue-hoc/spec/functional.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import sinon from 'sinon';
3 | import {mount} from 'vuenit';
4 | import {createHOC} from '../src';
5 |
6 | const Component = {
7 | props : ['propA', 'propB'],
8 | template: `
9 | {{propA}}
10 | {{propB}}
11 | `
12 | };
13 |
14 | mount(Component);
15 |
16 | test('can be a functional component', t => {
17 | const enhanced = createHOC(Component, {
18 | functional: true
19 | }, {
20 | props: {
21 | propB: 'baz'
22 | }
23 | });
24 | const vm = mount(enhanced, {
25 | props: {
26 | propA: 'foo',
27 | propB: 'bah'
28 | }
29 | });
30 |
31 | const list = vm.$find('li');
32 |
33 | t.is(list[0].$text, 'foo');
34 | t.is(list[1].$text, 'baz');
35 | });
36 |
--------------------------------------------------------------------------------
/packages/vue-hoc/spec/hooks.js:
--------------------------------------------------------------------------------
1 | /* globals require */
2 | const hooks = require('require-extension-hooks');
3 | const browserEnv = require('browser-env');
4 | browserEnv();
5 | hooks('js').plugin('babel');
6 |
--------------------------------------------------------------------------------
/packages/vue-hoc/spec/hooks.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import sinon from 'sinon';
3 | import {mount} from 'vuenit';
4 | import {createHOC, createRenderFn} from '../src';
5 |
6 | const Component = {
7 | template : `
`,
8 | };
9 | mount(Component);
10 |
11 | test('adds a created hook', t => {
12 | const spy = sinon.spy();
13 | const hoc = createHOC(Component, {
14 | created(){
15 | spy();
16 | },
17 | });
18 | const vm = mount(hoc);
19 |
20 | t.true(spy.called);
21 | });
22 |
--------------------------------------------------------------------------------
/packages/vue-hoc/spec/props.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import sinon from 'sinon';
3 | import {mount} from 'vuenit';
4 | import {createHOC, createHOCc, createRenderFn, createRenderFnc} from '../src';
5 |
6 | const Component = {
7 | props : ['propA', 'propB'],
8 | template: `
9 | {{propA}}
10 | {{propB}}
11 | `
12 | };
13 |
14 | mount(Component);
15 |
16 | test('props filter through hoc into component', t => {
17 | const enhance = createHOCc({
18 | });
19 | const enhanced = enhance(Component);
20 |
21 | const vm = mount(enhanced, {
22 | props : {
23 | propA : 'foo',
24 | propB: 'bah',
25 | }
26 | });
27 |
28 | const expected = '';
29 |
30 | t.is(vm.$html, expected);
31 | });
32 |
33 | test('can overwrite prop values', t => {
34 | const enhance = createHOCc({
35 | render : createRenderFnc({
36 | props : {
37 | propA : 'bah',
38 | propB : 'foo'
39 | }
40 | })
41 | }, null);
42 | const enhanced = enhance(Component);
43 |
44 | const vm = mount(enhanced, {
45 | props : {
46 | propA : 'foo',
47 | propB: 'bah',
48 | }
49 | });
50 |
51 | const expected = '';
52 |
53 | t.is(vm.$html, expected);
54 | });
55 |
56 | test('can amend prop values with a function', t => {
57 | const enhance = createHOCc({
58 | render: createRenderFnc({
59 | props(props){
60 | return {
61 | propA : this.$props.propB,
62 | propB : props.propA,
63 | };
64 | }
65 | })
66 | }, null);
67 | const enhanced = enhance(Component);
68 |
69 | const vm = mount(enhanced, {
70 | props : {
71 | propA : 'foo',
72 | propB: 'bah',
73 | }
74 | });
75 |
76 | const expected = '';
77 |
78 | t.is(vm.$html, expected);
79 | });
80 |
81 | test('can add additional props', t => {
82 | const enhance = createHOCc({
83 | props : ['propB', 'propC'],
84 | render : createRenderFnc({
85 | props(props){
86 | t.is(Object.hasOwnProperty.call(props, 'propA'), true);
87 | t.is(Object.hasOwnProperty.call(props, 'propB'), true);
88 | return {
89 | propA : props.propC[0],
90 | propB : props.propC[1]
91 | };
92 | }
93 | })
94 | }, null);
95 | const enhanced = enhance(Component);
96 |
97 | const vm = mount(enhanced, {
98 | props : {
99 | propA : 'foo',
100 | propB : 'bah',
101 | propC : ['abc', 'def']
102 | }
103 | });
104 |
105 | const expected = '';
106 |
107 | t.is(vm.$html, expected);
108 | });
109 |
110 | test('can pass props through multiple hocs', t => {
111 | const hoc1 = createHOCc({}, {})(Component);
112 | const hoc2 = createHOCc({}, {})(hoc1);
113 |
114 | const vm = mount(hoc2, {
115 | props : {
116 | propA : 'foo',
117 | propB : 'bah',
118 | }
119 | });
120 |
121 | const expected = '';
122 |
123 | t.is(vm.$html, expected);
124 | });
125 |
126 | test('unkonwn props are passed as attributes', t => {
127 | const withProp = createHOCc(null, {
128 | props: {
129 | unknown: 'some-value'
130 | },
131 | });
132 | const withCreated = createHOCc({
133 | created(){
134 | t.is(this.unknown, undefined);
135 | t.is(this.$props.unknown, undefined);
136 | t.is(this.$attrs.unknown, 'some-value');
137 | },
138 | });
139 | const hoc = withProp(withCreated(Component));
140 |
141 | const html = mount(hoc).$html;
142 |
143 | const expected = '';
144 |
145 | t.is(html, expected);
146 | });
147 |
148 | test('does not overwrite existing attributes', t => {
149 | const withProp = createHOCc(null, {
150 | props: {
151 | unknown: 'some-value'
152 | },
153 | attrs: {
154 | unknown: 'unknown',
155 | },
156 | });
157 | const withCreated = createHOCc({
158 | created(){
159 | t.is(this.unknown, undefined);
160 | t.is(this.$props.unknown, undefined);
161 | t.is(this.$attrs.unknown, 'unknown');
162 | },
163 | });
164 | const hoc = withProp(withCreated(Component));
165 |
166 | mount(hoc);
167 | });
168 |
169 | test('does not include known props', t => {
170 | const withProp = createHOCc(null, {
171 | props: {
172 | unknown: 'some-value',
173 | propA: 'some-value',
174 | },
175 | });
176 | const withCreated = createHOCc({
177 | created(){
178 | t.is(this.propA, 'some-value');
179 | t.is(this.$props.propA, 'some-value');
180 | t.is(this.$attrs.propA, undefined);
181 | },
182 | });
183 | const hoc = withProp(withCreated(Component));
184 |
185 | mount(hoc);
186 | });
187 |
188 | test('knows about mixin props', t => {
189 | const C = Object.assign({}, Component, {
190 | mixins: [
191 | {
192 | props: [ 'mixinProp' ],
193 | },
194 | ],
195 | });
196 | const withProp = createHOCc(null, {
197 | props: {
198 | mixinProp: 'some-value',
199 | },
200 | });
201 | const withCreated = createHOCc({
202 | created(){
203 | t.is(this.mixinProp, 'some-value');
204 | },
205 | });
206 | const hoc = withProp(withCreated(C));
207 |
208 | const html = mount(hoc).$html;
209 |
210 | const expected = '';
211 |
212 | t.is(html, expected);
213 | });
214 |
215 | test('overrides mixin props with component-level props', t => {
216 | const C = Object.assign({}, Component, {
217 | props: {
218 | mixinProp: {
219 | default: 'from-component',
220 | },
221 | },
222 | mixins: [
223 | {
224 | props: {
225 | mixinProp: {
226 | default: 'from-mixin',
227 | },
228 | },
229 | },
230 | ],
231 | });
232 | const withCreated = createHOCc({
233 | created(){
234 | t.is(this.mixinProp, 'from-component');
235 | },
236 | });
237 | const hoc = withCreated(C);
238 |
239 | const html = mount(hoc).$html;
240 |
241 | const expected = '';
242 |
243 | t.is(html, expected);
244 | });
245 |
246 | test('overrides component props with hoc props', t => {
247 | const C = Object.assign({}, Component, {
248 | props: {
249 | mixinProp: {
250 | default: 'from-component',
251 | },
252 | },
253 | mixins: [
254 | {
255 | props: {
256 | mixinProp: {
257 | default: 'from-mixin',
258 | },
259 | },
260 | },
261 | ],
262 | });
263 | const withCreated = createHOCc({
264 | props: {
265 | mixinProp: {
266 | default: 'from-hoc',
267 | },
268 | },
269 | created(){
270 | t.is(this.mixinProp, 'from-hoc');
271 | },
272 | });
273 | const hoc = withCreated(C);
274 |
275 | const html = mount(hoc).$html;
276 |
277 | const expected = '';
278 |
279 | t.is(html, expected);
280 | });
281 |
282 | test('it recursively collects mixin props', t => {
283 | const C = Object.assign({}, Component, {
284 | mixins: [
285 | {
286 | mixins: [
287 | {
288 | props: {
289 | mixinProp: {
290 | default: 'from-deep-mixin',
291 | },
292 | },
293 | },
294 | ],
295 | },
296 | ],
297 | });
298 | const withCreated = createHOCc({
299 | created(){
300 | t.is(this.mixinProp, 'from-deep-mixin');
301 | },
302 | });
303 | const hoc = withCreated(C);
304 |
305 | const html = mount(hoc).$html;
306 |
307 | const expected = '';
308 |
309 | t.is(html, expected);
310 | });
311 |
312 | test('it takes the highest mixin prop', t => {
313 | const C = Object.assign({}, Component, {
314 | mixins: [
315 | {
316 | props: {
317 | mixinProp: {
318 | default: 'from-shallow-mixin',
319 | },
320 | },
321 | mixins: [
322 | {
323 | props: {
324 | mixinProp: {
325 | default: 'from-deep-mixin',
326 | },
327 | },
328 | },
329 | ],
330 | },
331 | ],
332 | });
333 | const withCreated = createHOCc({
334 | created(){
335 | t.is(this.mixinProp, 'from-shallow-mixin');
336 | },
337 | });
338 | const hoc = withCreated(C);
339 |
340 | const html = mount(hoc).$html;
341 |
342 | const expected = '';
343 |
344 | t.is(html, expected);
345 | });
346 |
--------------------------------------------------------------------------------
/packages/vue-hoc/spec/slots.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import sinon from 'sinon';
3 | import {mount} from 'vuenit';
4 | import {
5 | createHOC,
6 | createRenderFn,
7 | createHOCc,
8 | } from '../src';
9 |
10 | const Component = {
11 | template : `
12 |
13 |
14 |
15 |
16 |
17 |
`
18 | };
19 | mount(Component);
20 |
21 | const Dynamic = {
22 | template: `
23 |
24 | other differently
25 | named differently
26 | named differently
27 |
28 | `,
29 | props: {
30 | wrappedComponent: Object
31 | }
32 | };
33 | mount(Dynamic);
34 |
35 | test('it renders a slot', t => {
36 | const vm = mount(Component, {
37 | slots: {
38 | default: '
',
39 | named_slot: '
'
40 | }
41 | });
42 | t.true(vm.$contains('#default'));
43 | t.true(vm.$contains('#named'));
44 | });
45 |
46 | test('it passes slots through a hoc', t => {
47 | const hoc = createHOC(Component);
48 | const vm = mount(hoc, {
49 | slots: {
50 | default: '
',
51 | named_slot: '
'
52 | }
53 | });
54 | t.true(vm.$contains('#default'));
55 | t.true(vm.$contains('#named'));
56 | });
57 |
58 | test('passes slots through multiple hocs', t => {
59 | const hoc1 = createHOC(Component);
60 | const hoc2 = createHOC(hoc1);
61 | const vm = mount(hoc2, {
62 | slots: {
63 | default: '
',
64 | named_slot: '
'
65 | }
66 | });
67 | t.true(vm.$contains('#default'));
68 | t.true(vm.$contains('#named'));
69 | });
70 |
71 | test('it renders multiple slots', t => {
72 | const hoc = createHOC(createHOC(Component));
73 | const vm = mount(hoc, {
74 | innerHTML: 'first
second
third
fourth
',
75 | });
76 | t.true(vm.$contains('#first'));
77 | t.true(vm.$contains('#second'));
78 | t.true(vm.$contains('#third'));
79 | t.true(vm.$contains('#fourth'));
80 | });
81 | test('it renders named slots in order', t => {
82 | const hoc =createHOC(Component);
83 | const vm = mount(hoc, {
84 | innerHTML: `
85 | other
86 | default
87 | named
88 | `,
89 | });
90 | const html = vm.$html;
91 |
92 | t.is(html, '');
93 | });
94 |
95 | test('(Component) it renders text slot content', t => {
96 | const hoc = createHOC(Component);
97 | const vm = mount(hoc, {
98 | slots: 'some text',
99 | });
100 | const html = vm.$html;
101 |
102 | t.true(html.includes('some text'));
103 | });
104 | test('it renders dynamic named slots in order', t => {
105 | const hoc = createHOC(Component);
106 | const vm = mount(hoc, {
107 | innerHTML: `
108 | other
109 | default
110 | named
111 | `,
112 | });
113 | const html = vm.$html;
114 |
115 | t.is(html, '');
116 | });
117 | test('it renders template slots in dynamic component', t => {
118 | const hoc = createHOC(Dynamic, null, {
119 | props: {
120 | wrappedComponent: Component
121 | }
122 | });
123 | const vm = mount(hoc, {
124 | innerHTML: `
125 | other
126 | default
127 | named
named 2
128 | default 2
129 | `,
130 | });
131 | const html = vm.$html;
132 |
133 | t.is(html, 'default
default 2
named
named 2
');
134 | });
135 |
136 | test('(string) it renders text slot content', t => {
137 | const hoc = createHOC('button');
138 | const vm = mount(hoc, {
139 | slots: 'some text',
140 | });
141 | const html = vm.$html;
142 | t.true(html.includes('some text'));
143 | });
144 |
145 | test('(Component) it renders a mix of text and tags', t => {
146 | const hoc = createHOC(Component);
147 | const vm = mount(hoc, {
148 | slots: 'some text icon some more text',
149 | });
150 | const html = vm.$html;
151 |
152 | t.true(vm.$contains('#icon'));
153 | t.true(html.includes('some text'));
154 | t.true(html.includes('some more text'));
155 | });
156 |
157 | test('(string) it renders a mix of text and tags', t => {
158 | const hoc = createHOC('button');
159 | const vm = mount(hoc, {
160 | slots: 'some text icon some more text',
161 | });
162 | const html = vm.$html;
163 |
164 | t.true(vm.$contains('#icon'));
165 | t.true(html.includes('some text'));
166 | t.true(html.includes('some more text'));
167 | });
168 |
169 | test('(Component) it renders a mix of tags and text', t => {
170 | const hoc = createHOC(Component);
171 | const vm = mount(hoc, {
172 | slots: 'icon some text',
173 | });
174 | const html = vm.$html;
175 |
176 | t.true(vm.$contains('#icon'));
177 | t.true(html.includes('some text'));
178 | });
179 |
180 | test('(string) it renders a mix of tags and text', t => {
181 | const hoc = createHOC('button');
182 | const vm = mount(hoc, {
183 | slots: 'icon some text',
184 | });
185 | const html = vm.$html;
186 |
187 | t.true(vm.$contains('#icon'));
188 | t.true(html.includes('some text'));
189 | });
190 |
191 | test('(Component) it renders a mix of tags text and templates', t => {
192 | const hoc = createHOC(Component);
193 | const vm = mount(hoc, {
194 | slots: 'icon some textsome template stuff
! ',
195 | });
196 | const html = vm.$html;
197 |
198 | t.true(vm.$contains('#icon'));
199 | t.true(html.includes('some text'));
200 | t.true(html.includes('some template stuff'));
201 | });
202 |
203 | test('(string) it renders a mix of tags text and templates', t => {
204 | const hoc = createHOC('button');
205 | const vm = mount(hoc, {
206 | slots: 'icon some textsome template stuff
! ',
207 | });
208 | const html = vm.$html;
209 |
210 | t.true(vm.$contains('#icon'));
211 | t.true(html.includes('some text'));
212 | t.true(html.includes('some template stuff'));
213 | });
214 |
215 | test('provide string element in a curried hoc should not contain element not provided', t => {
216 | const hoc = createHOCc(null, null)('div')
217 | const vm = mount(hoc, {
218 | slots: {
219 | default:
220 | 'foo' +
221 | 'another foo
'
222 | }
223 | });
224 |
225 | t.is(vm.$html,
226 | '' +
227 | 'foo' +
228 | '
another foo
' +
229 | '
'
230 | );
231 | t.false(vm.$html.includes(''));
232 | });
233 |
--------------------------------------------------------------------------------
/packages/vue-hoc/spec/templateSlots.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import {mount} from 'vuenit';
3 | import {createHOC} from '../src';
4 |
5 | const Component = {
6 | template : `
7 |
component default slot
8 |
component named slot
9 |
`
10 | };
11 | mount(Component);
12 |
13 | test('passes named slots by `template` tag', t => {
14 | const hoc = createHOC(Component);
15 | const vm = mount(hoc, {
16 | slots: {
17 | named_slot: 'hoc template slot '
18 | }
19 | });
20 | t.true(vm.$findOne('#named').$text === 'hoc template slot');
21 | });
22 |
23 | test('passes default slots by `template` tag', t => {
24 | const hoc = createHOC(Component);
25 | const vm = mount(hoc, {
26 | slots: {
27 | default: 'default slot '
28 | }
29 | });
30 | t.true(vm.$findOne('#default').$text === 'default slot');
31 | });
32 |
33 | test('passes both default slots and named slots by `template` tag', t => {
34 | const hoc = createHOC(Component);
35 | const vm = mount(hoc, {
36 | slots: {
37 | default: 'default slot ',
38 | named_slot: 'hoc template slot '
39 | }
40 | });
41 | t.true(vm.$findOne('#default').$text === 'default slot');
42 | t.true(vm.$findOne('#named').$text === 'hoc template slot');
43 | });
44 |
45 | test('passes default slots by `div` tag and named slots by `template` tag', t => {
46 | const hoc = createHOC(Component);
47 | const vm = mount(hoc, {
48 | slots: {
49 | default: 'default slot
',
50 | named_slot: 'hoc template slot '
51 | }
52 | });
53 |
54 | t.true(vm.$findOne('#default').$html === '');
55 | t.true(vm.$findOne('#named').$html === 'hoc template slot
');
56 | });
57 |
58 | test('passes named slots by `div` tag and default slots by `template` tag', t => {
59 | const hoc = createHOC(Component);
60 | const vm = mount(hoc, {
61 | slots: {
62 | default: 'default slot ',
63 | named_slot: 'hoc template slot
'
64 | }
65 | });
66 |
67 | t.true(vm.$findOne('#default').$html === 'default slot
');
68 | t.true(vm.$findOne('#named').$html === '');
69 | });
70 |
71 | test('passes named template slots through multiple hocs', t => {
72 | const hoc1 = createHOC(Component);
73 | const hoc2 = createHOC(hoc1);
74 | const vm = mount(hoc2, {
75 | slots: {
76 | named_slot: 'hoc template slot '
77 | }
78 | });
79 | t.true(vm.$findOne('#named').$text === 'hoc template slot');
80 | });
81 |
--------------------------------------------------------------------------------
/packages/vue-hoc/src/constants.js:
--------------------------------------------------------------------------------
1 | export const CURRIED = '@@VUE_HOC/CURRIED';
2 |
--------------------------------------------------------------------------------
/packages/vue-hoc/src/createHOC.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import { createRenderFnc } from './createRenderFn';
3 | import getProps from './getProps';
4 | import getComponentOptions from './getComponentOptions';
5 | import { CURRIED } from './constants';
6 |
7 | const defaultStrategy = (parent, child) => child;
8 |
9 | export const createHOC = (Component, options, renderOptions) => {
10 | const target = getComponentOptions(Component);
11 |
12 | const hoc = {
13 | props: getProps(target),
14 | mixins: [],
15 | name: `${target.name || 'Anonymous'}HOC`,
16 | render: createRenderFnc(renderOptions),
17 | };
18 |
19 | if (options){
20 | // merge options into the hoc
21 | // we piggyback off Vue's optionMergeStrategies so we get the same
22 | // merging behavior as with mixins
23 | Object.keys(options).forEach((key) => {
24 | let child = options && options[key];
25 | const parent = hoc[key];
26 | const strategy = Vue.config.optionMergeStrategies[key] || defaultStrategy;
27 |
28 | // props are unique as we have a specific normaliser
29 | if (key === 'props') {
30 | child = getProps(options);
31 | }
32 |
33 | hoc[key] = strategy(parent, child);
34 | });
35 | }
36 |
37 | hoc.mixins && hoc.mixins.push({
38 | created(){
39 | this.$createElement = this.$vnode.context.$createElement;
40 | this._c = this.$vnode.context._c;
41 | }
42 | });
43 |
44 | if (hoc.render && hoc.render[CURRIED]){
45 | hoc.render = hoc.render(Component);
46 | }
47 |
48 | return hoc;
49 | };
50 |
51 | export const createHOCc = (
52 | options,
53 | renderOptions,
54 | ) => {
55 | const curried = (Component) => createHOC(Component, options, renderOptions);
56 | curried[CURRIED] = true;
57 | return curried;
58 | };
59 |
60 | export default createHOC;
61 |
--------------------------------------------------------------------------------
/packages/vue-hoc/src/createRenderFn.js:
--------------------------------------------------------------------------------
1 | import normalizeSlots from './normalizeSlots';
2 | import getProps from './getProps';
3 | import { CURRIED } from './constants';
4 | import {
5 | assign,
6 | isObject,
7 | isFunction,
8 | } from './utils';
9 |
10 | // most options can provide a factory function to calculate the value at render time
11 | // but these options are already menat to be functions, so we don't invoke them
12 | // during the hoc creation phase
13 | const justBindOptions = [
14 | 'listeners',
15 | 'nativeOn',
16 | 'scopedSlots',
17 | ];
18 |
19 | const justBindFn = (key) => justBindOptions.indexOf(key) > -1;
20 |
21 | // ensures the keys always contain listeners/props/attrs
22 | const getOptionsKeys = (options) => Object
23 | .keys(options)
24 | .concat(['listeners', 'props', 'attrs'])
25 | .filter((option, i, arr) => arr.indexOf(option) === i);
26 |
27 | // for every option, we want to have a factory function that returns
28 | // the actual result
29 | const createOptionHandlers = (originalOptions, keys) => {
30 | const options = {};
31 |
32 | keys.forEach((key) => {
33 | const option = originalOptions[key];
34 |
35 | // if option is not provided, default to returning the initial value
36 | if (!option){
37 | options[key] = (owner) => owner;
38 | return;
39 | }
40 |
41 | // option is a factory function
42 | if (isFunction(option)){
43 | options[key] = option;
44 | return;
45 | }
46 |
47 | // option is an object, we need to handle each property directly
48 | if (isObject(option)){
49 | const optionKeys = Object.keys(option);
50 | const hasFactories = optionKeys.some((key) => isFunction(option[key]));
51 |
52 | // no factory functions, just merge the parent/child property
53 | if (!hasFactories){
54 | options[key] = (owner) => assign({}, owner, option);
55 | return;
56 | }
57 |
58 | options[key] = function(owner) {
59 | const result = assign({}, owner);
60 | const justBind = justBindFn(key);
61 |
62 | optionKeys.forEach((key) => {
63 | let value = option && option[key];
64 |
65 | if (isFunction(value)){
66 | // some properties expect functions
67 | if (justBind){
68 | value = value.bind(this);
69 | // for everything else, invoke the function to get the value
70 | }else{
71 | value = value.call(this, owner);
72 | }
73 | }
74 | result[key] = value;
75 | });
76 | return result;
77 | };
78 | return;
79 | }
80 |
81 | // for anything else, just return the option value
82 | options[key] = () => option;
83 | });
84 |
85 | return options;
86 | };
87 |
88 | // prepares the options so during render, we can quickly process them
89 | const preprocessOptions = (originalOptions) => {
90 | const keys = getOptionsKeys(originalOptions);
91 | const options = createOptionHandlers(originalOptions, keys);
92 |
93 | return (context, isFunctional) => {
94 | const result = {
95 | on: {},
96 | props: {},
97 | attrs: {},
98 | };
99 |
100 | keys.forEach((key) => {
101 | // get this component's value
102 | const owner = isFunctional ?
103 | context[key] || context.data[key] :
104 | context[`$${key}`];
105 |
106 | // call the option handler
107 | const value = options[key].call(context, owner);
108 |
109 | // listeners has to be awkward and be renamed to on
110 | if (key === 'listeners'){
111 | key = 'on';
112 | }
113 |
114 | result[key] = value;
115 | });
116 |
117 | return result;
118 | };
119 | };
120 |
121 | // any unknown props need to be passed through as attrs
122 | const getUnusedProps = (Component, props) => {
123 | const result = {};
124 | const target = getProps(Component);
125 |
126 | Object.keys(props).forEach((prop) => {
127 | if (target[prop] === undefined) {
128 | result[prop] = props[prop];
129 | }
130 | });
131 |
132 | return result;
133 | };
134 |
135 | const statelessRenderFn = (Component, getData, h, context) => {
136 | const data = getData(context, true);
137 | const scopedSlots = context.data.scopedSlots;
138 | const slots = context.children || [];
139 | const unusedProps = getUnusedProps(Component, data.props);
140 |
141 | data.scopedSlots = data.scopedSlots || scopedSlots;
142 | data.attrs = assign({}, unusedProps, data.attrs);
143 |
144 | return h(Component, data, slots);
145 | };
146 | const statefulRenderFn = (Component, getData, h, context) => {
147 | const data = getData(context, false);
148 | const scopedSlots = context.$scopedSlots;
149 | const slots = normalizeSlots(context.$slots, context.$vnode.context) || [];
150 | const unusedProps = getUnusedProps(Component, data.props);
151 |
152 | data.scopedSlots = data.scopedSlots || scopedSlots;
153 | data.attrs = assign({}, unusedProps, data.attrs);
154 |
155 | return h(Component, data, slots);
156 | };
157 |
158 | export const createRenderFn = (Component, options) => {
159 | const getData = preprocessOptions(options || {});
160 |
161 | return function renderHoc(h, context) {
162 | return context ?
163 | statelessRenderFn(Component, getData, h, context) :
164 | statefulRenderFn(Component, getData, h, this);
165 | };
166 | };
167 |
168 | export const createRenderFnc = (options) => {
169 | const curried = (Component) => createRenderFn(Component, options);
170 | curried[CURRIED] = true;
171 | return curried;
172 | };
173 |
--------------------------------------------------------------------------------
/packages/vue-hoc/src/getComponentOptions.js:
--------------------------------------------------------------------------------
1 | import { isFunction } from './utils';
2 |
3 | export default (Component) => (isFunction(Component)) ? Component.options : Component;
4 |
--------------------------------------------------------------------------------
/packages/vue-hoc/src/getProps.js:
--------------------------------------------------------------------------------
1 | import { assign, isArray } from './utils';
2 | import getComponentOptions from './getComponentOptions';
3 |
4 | const normalize = (props) => {
5 | if (!props) {
6 | return {};
7 | }
8 | if (isArray(props)) {
9 | const result = {};
10 | props.forEach((key) => {
11 | if (typeof key === 'string') {
12 | result[key] = {};
13 | }
14 | });
15 | return result;
16 | }
17 | return assign({}, props);
18 | };
19 |
20 | const mergeMixinProps = (mixins, initial = {}) => {
21 | if (!mixins || !mixins.length) {
22 | return initial;
23 | }
24 |
25 | return mixins.reduce((result, mixin) => {
26 | const props = assign(
27 | {},
28 | mergeMixinProps(mixin.mixins, result),
29 | normalize(mixin.props),
30 | );
31 |
32 | return assign({}, result, props);
33 | }, initial);
34 | };
35 |
36 | const getProps = (Component) => {
37 | const options = getComponentOptions(Component);
38 | const props = normalize(options.props);
39 | const mixinProps = mergeMixinProps(options.mixins);
40 |
41 | return assign({}, mixinProps, props);
42 | };
43 |
44 | export default getProps;
45 |
--------------------------------------------------------------------------------
/packages/vue-hoc/src/index.js:
--------------------------------------------------------------------------------
1 | import normalizeSlots from './normalizeSlots';
2 | import {createRenderFn, createRenderFnc} from './createRenderFn';
3 | import {createHOC, createHOCc} from './createHOC';
4 |
5 | export {
6 | normalizeSlots,
7 | createRenderFn,
8 | createRenderFnc,
9 | createHOC,
10 | createHOCc
11 | };
12 |
13 | export default {
14 | normalizeSlots,
15 | createRenderFn,
16 | createRenderFnc,
17 | createHOC,
18 | createHOCc
19 | };
20 |
--------------------------------------------------------------------------------
/packages/vue-hoc/src/normalizeSlots.js:
--------------------------------------------------------------------------------
1 | const normalizeSlots = (slots, context) => Object.keys(slots)
2 | .reduce((arr, key) => {
3 | slots[key].forEach((vnode) => {
4 | if (!vnode.context) {
5 | slots[key].context = context;
6 | }
7 | if (!vnode.data) {
8 | vnode.data = {};
9 | }
10 | vnode.data.slot = key;
11 | });
12 | return arr.concat(slots[key]);
13 | }, []);
14 |
15 | export default normalizeSlots;
16 |
--------------------------------------------------------------------------------
/packages/vue-hoc/src/utils.js:
--------------------------------------------------------------------------------
1 | // IE9-11 do not support `Object.assign`
2 | const poly = function (target, ...sources) {
3 | if (target == null) {
4 | throw new TypeError('Uncaught TypeError: Cannot convert undefined or null to object');
5 | }
6 |
7 | for (let i = 0, il = sources.length; i < il; i += 1) {
8 | const source = sources[i];
9 | if (source == null) {
10 | continue;
11 | }
12 |
13 | for (let key in source) {
14 | if (Object.hasOwnProperty.call(source, key)) {
15 | Object.defineProperty(target, key, {
16 | enumerable: true,
17 | writable: true,
18 | value: source[key],
19 | });
20 | }
21 | }
22 | }
23 |
24 | // $FlowFixMe
25 | return target;
26 | };
27 |
28 | export const assign = Object.assign || poly;
29 |
30 | export const isObject = (test) => test && Object.prototype.toString.call(test) === '[object Object]';
31 |
32 | export const isFunction = (test) => typeof test === 'function';
33 |
34 | export const isArray = Array.isArray;
35 |
--------------------------------------------------------------------------------
/packages/vuex-compose/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "production": {
4 | "presets" : [
5 | ["env", {
6 | "targets" : {
7 | "browsers" : ["last 2 versions"]
8 | },
9 | "modules": false
10 | }]
11 | ],
12 | "sourceMaps" : true,
13 | "plugins": ["external-helpers"],
14 | },
15 | "test": {
16 | "presets" : [
17 | ["env", {
18 | "targets" : {
19 | "browsers" : ["last 2 versions"],
20 | "node" : "current",
21 | },
22 | "modules": "commonjs"
23 | }]
24 | ],
25 | "sourceMaps" : true,
26 | "plugins": []
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/packages/vuex-compose/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "parser": "babel-eslint",
3 | "plugins": [],
4 | "env": {
5 | "browser": true,
6 | "es6": true
7 | },
8 | "extends": [
9 | "eslint:recommended",
10 | "plugin:import/errors",
11 | "plugin:import/warnings",
12 | ],
13 | "parserOptions": {
14 | "sourceType": "module"
15 | },
16 | "rules": {
17 | "indent": [
18 | "error",
19 | 2
20 | ],
21 | "linebreak-style": [
22 | "error",
23 | "unix"
24 | ],
25 | "quotes": [
26 | "error",
27 | "single"
28 | ],
29 | "semi": [
30 | "error",
31 | "always"
32 | ]
33 | }
34 | };
35 |
--------------------------------------------------------------------------------
/packages/vuex-compose/.npmignore:
--------------------------------------------------------------------------------
1 | src
2 | spec
3 |
4 | # Logs
5 | logs
6 | *.log
7 | npm-debug.log*
8 | yarn-debug.log*
9 | yarn-error.log*
10 |
11 | # Runtime data
12 | pids
13 | *.pid
14 | *.seed
15 | *.pid.lock
16 |
17 | # Directory for instrumented libs generated by jscoverage/JSCover
18 | lib-cov
19 |
20 | # Coverage directory used by tools like istanbul
21 | coverage
22 |
23 | # nyc test coverage
24 | .nyc_output
25 |
26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
27 | .grunt
28 |
29 | # Bower dependency directory (https://bower.io/)
30 | bower_components
31 |
32 | # node-waf configuration
33 | .lock-wscript
34 |
35 | # Compiled binary addons (http://nodejs.org/api/addons.html)
36 | build/Release
37 |
38 | # Dependency directories
39 | node_modules/
40 | jspm_packages/
41 |
42 | # Typescript v1 declaration files
43 | typings/
44 |
45 | # Optional npm cache directory
46 | .npm
47 |
48 | # Optional eslint cache
49 | .eslintcache
50 |
51 | # Optional REPL history
52 | .node_repl_history
53 |
54 | # Output of 'npm pack'
55 | *.tgz
56 |
57 | # Yarn Integrity file
58 | .yarn-integrity
59 |
60 | # dotenv environment variables file
61 | .env
62 |
--------------------------------------------------------------------------------
/packages/vuex-compose/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 | # [1.0.0](https://github.com/jackmellis/vue-hoc/compare/0.3.0...1.0.0) (2018-04-12)
3 |
4 |
5 | ### Features
6 |
7 | * **vuex-compose:** create vuex-compose package ([0a3691d](https://github.com/jackmellis/vue-hoc/commit/0a3691d))
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/packages/vuex-compose/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/packages/vuex-compose/README.md:
--------------------------------------------------------------------------------
1 | # Vuex Compose
2 |
3 | ## API
4 |
5 | ### mapStateToProps
6 | *alias: withState*
7 | ```js
8 | (
9 | namespace?: string,
10 | map: Array | { [key]: string | Function },
11 | ) => (Component) => Component
12 | ```
13 | Maps state to props, effectively the same as using Vuex's `mapState` within your component.
14 |
15 | Namespacing is slightly different to `mapState`. If you have a module where `namespaced: false`, you can still provide a namespace to drill down into the module's state.
16 |
17 | ```js
18 | mapStateToProps('myModule', {
19 | myPropName: 'myStateValue',
20 | });
21 | ```
22 |
23 | ### mapGettersToProps
24 | *alias: withGetters*
25 | ```js
26 | (
27 | namespace?: string,
28 | map: Array,
29 | ) => (Component) => Component
30 | ```
31 |
32 | ### mapGettersToComputed
33 | ```js
34 | (
35 | namespace?: string,
36 | map: Array,
37 | ) => (Component) => Component
38 | ```
39 |
40 | ### mapMutationsToHandlers
41 | *alias: withMutations*
42 | ```js
43 | (
44 | mutations: {
45 | [key]: string,
46 | },
47 | ) => (Componet) => Component
48 | ```
49 | Maps mutations to event handlers. When the specified key is emitted, the mutation will be committed with the provided payload.
50 |
51 | ```js
52 | compose(
53 | mapMutationsToHandlers({
54 | submit: 'SUBMIT',
55 | }),
56 | lifecycle({
57 | created(){
58 | this.$emit('submit', this.payloadValues);
59 | },
60 | }),
61 | )
62 | ```
63 |
64 | ### mapActionsToHandlers
65 | *alias: withActions*
66 | ```js
67 | (
68 | namespace?: string,
69 | map: {
70 | [key]: string,
71 | },
72 | ) => (Component) => Component
73 | ```
74 |
75 | ### mapActionsToMethods
76 | ```js
77 | (
78 | namespace?: string,
79 | map: {
80 | [key]: string,
81 | },
82 | ) => (Component) => Component
83 | ```
84 |
85 | ### mapActionsToProps
86 | ```js
87 | (
88 | namespace?: string,
89 | map: {
90 | [key]: string,
91 | },
92 | ) => (Component) => Component
93 | ```
94 |
95 | ### mapActionCreatorsToHandlers
96 | *alias: withActionCreators*
97 | ```js
98 | (
99 | map: {
100 | [key]: Function,
101 | },
102 | ) => (Component) => Component
103 | ```
104 |
105 | ### mapActionCreatorsToMethods
106 | ```js
107 | (
108 | map: {
109 | [key]: Function,
110 | },
111 | ) => (Component) => Component
112 | ```
113 |
114 | ### mapActionCreatorsToProps
115 | ```js
116 | (
117 | map: {
118 | [key]: Function,
119 | },
120 | ) => (Component) => Component
121 | ```
122 |
123 | ### connect
124 | ```ja
125 | (
126 | namespace?: string,
127 | mapStateToProps?: Array | { [key]: string | Function },
128 | mapActionsToHandlers?: { [key]: string },
129 | mapGettersToProps?: Array,
130 | ) => (Component) => Component
131 | ```
132 |
133 | ### registerModule
134 | ```js
135 | (
136 | namespace: string | Array,
137 | module: Object,
138 | )
139 | ```
140 | Registers a provided module, only if it has not already been registered previously.
141 |
142 | ### compose
143 | ```js
144 | (
145 | ...hocCreators: Array
146 | ) => (Component) => Component;
147 | ```
148 |
--------------------------------------------------------------------------------
/packages/vuex-compose/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vuex-compose",
3 | "version": "0.0.0",
4 | "description": "Connect components through HOCs",
5 | "main": "dist/vuex-compose.js",
6 | "module": "dist/vuex-compose.es.js",
7 | "scripts": {
8 | "test": "ava",
9 | "coverage": "nyc --reporter=html yarn test",
10 | "debug": "inspect ../../node_modules/ava/profile",
11 | "lint": "eslint src",
12 | "build": "rm -rf dist && cross-env NODE_ENV=production rollup -c",
13 | "prepublish": "yarn build"
14 | },
15 | "ava": {
16 | "files": [
17 | "spec/**/*.spec.js"
18 | ],
19 | "source": [
20 | "src/**/*.js"
21 | ],
22 | "require": [
23 | "./spec/hooks.js"
24 | ]
25 | },
26 | "repository": {
27 | "type": "git",
28 | "url": "git+https://github.com/jackmellis/vue-hoc.git"
29 | },
30 | "author": "Jack Ellis",
31 | "license": "Apache-2.0",
32 | "bugs": {
33 | "url": "https://github.com/jackmellis/vue-hoc/issues"
34 | },
35 | "homepage": "https://github.com/jackmellis/vue-hoc#readme",
36 | "dependencies": {
37 | "vue-hoc": "link:../vue-hoc",
38 | "vue-compose": "link:../vue-compose"
39 | },
40 | "peerDependencies": {
41 | "vuex": "^3.0.0"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/packages/vuex-compose/rollup.config.js:
--------------------------------------------------------------------------------
1 | import babel from 'rollup-plugin-babel';
2 |
3 | export default {
4 | input: 'src/index.js',
5 | output: [
6 | {
7 | file: 'dist/vuex-compose.js',
8 | format: 'cjs',
9 | exports: 'named',
10 | },
11 | {
12 | file: 'dist/vuex-compose.es.js',
13 | format: 'es',
14 | exports: 'named',
15 | },
16 | ],
17 | plugins: [
18 | babel({
19 | exclude: 'node_modules/**',
20 | }),
21 | ],
22 | external: ['vue-hoc']
23 | };
24 |
--------------------------------------------------------------------------------
/packages/vuex-compose/spec/actions.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import sinon from 'sinon';
3 | import { mount, mockStore } from 'vuenit';
4 | import { createSink } from 'vue-compose';
5 | import vuex from 'vuex';
6 | import Vue from 'vue';
7 |
8 | import {
9 | mapActionsToProps,
10 | mapActionsToMethods,
11 | mapActionsToHandlers,
12 | mapActionCreatorsToProps,
13 | mapActionCreatorsToMethods,
14 | mapActionCreatorsToHandlers,
15 | } from 'vuex-compose';
16 |
17 | test('maps actions to handlers', t => {
18 | const ACTION = 'submit';
19 | const spy = sinon.spy();
20 | const store = mockStore({
21 | actions: {
22 | [ACTION]: spy,
23 | },
24 | });
25 | const C = createSink(function () {
26 | this.$emit('submit', 'xxx');
27 | });
28 | const Wrapper = mapActionsToHandlers({
29 | submit: ACTION,
30 | })(C);
31 |
32 | mount(Wrapper, {
33 | install: (Vue) => Vue.prototype.$store = store,
34 | });
35 |
36 | t.true(spy.called);
37 | t.is(spy.lastCall.args[1], 'xxx');
38 | });
39 |
40 | test('maps actions to methods', t => {
41 | const ACTION = 'submit';
42 | const spy = sinon.spy();
43 | const store = mockStore({
44 | actions: {
45 | [ACTION]: spy,
46 | },
47 | });
48 | const C = createSink(function () {
49 | this.submit('xxx');
50 | });
51 | const Wrapper = mapActionsToMethods({
52 | submit: ACTION,
53 | })(C);
54 |
55 | mount(Wrapper, {
56 | install: (Vue) => Vue.prototype.$store = store,
57 | });
58 |
59 | t.true(spy.called);
60 | t.is(spy.lastCall.args[1], 'xxx');
61 | });
62 |
63 | test('maps actions to props', t => {
64 | const ACTION = 'submit';
65 | const spy = sinon.spy();
66 | const store = mockStore({
67 | actions: {
68 | [ACTION]: spy,
69 | },
70 | });
71 | const C = createSink((props) => {
72 | props.onSubmit('xxx');
73 | });
74 | const Wrapper = mapActionsToProps({
75 | onSubmit: ACTION,
76 | })(C);
77 |
78 | mount(Wrapper, {
79 | install: (Vue) => Vue.prototype.$store = store,
80 | });
81 |
82 | t.true(spy.called);
83 | t.is(spy.lastCall.args[1], 'xxx');
84 | });
85 |
86 | test('maps action creators to handlers', t => {
87 | const ACTION = 'SUBMIT';
88 | const actionCreator = (foo) => ({
89 | type: ACTION,
90 | payload: {
91 | foo,
92 | },
93 | });
94 | const spy = sinon.spy();
95 | Vue.use(vuex);
96 | const store = new vuex.Store({
97 | actions: {
98 | [ACTION]: spy,
99 | },
100 | });
101 | const C = createSink(function() {
102 | this.$emit('submit', 'xxx');
103 | });
104 | const Wrapper = mapActionCreatorsToHandlers({
105 | submit: actionCreator,
106 | })(C);
107 |
108 | mount(Wrapper, {
109 | install: (Vue) => Vue.prototype.$store = store,
110 | });
111 |
112 | t.true(spy.called);
113 | t.is(spy.lastCall.args[1].payload.foo, 'xxx');
114 | });
115 |
116 | test('maps action creators to methods', t => {
117 | const ACTION = 'SUBMIT';
118 | const actionCreator = (foo) => ({
119 | type: ACTION,
120 | payload: {
121 | foo,
122 | },
123 | });
124 | const spy = sinon.spy();
125 | Vue.use(vuex);
126 | const store = new vuex.Store({
127 | actions: {
128 | [ACTION]: spy,
129 | },
130 | });
131 | const C = createSink(function() {
132 | this.submit('xxx');
133 | });
134 | const Wrapper = mapActionCreatorsToMethods({
135 | submit: actionCreator,
136 | })(C);
137 |
138 | mount(Wrapper, {
139 | install: (Vue) => Vue.prototype.$store = store,
140 | });
141 |
142 | t.true(spy.called);
143 | t.is(spy.lastCall.args[1].payload.foo, 'xxx');
144 | });
145 |
146 | test('maps action creators to props', t => {
147 | const ACTION = 'SUBMIT';
148 | const actionCreator = (foo) => ({
149 | type: ACTION,
150 | payload: {
151 | foo,
152 | },
153 | });
154 | const spy = sinon.spy();
155 | Vue.use(vuex);
156 | const store = new vuex.Store({
157 | actions: {
158 | [ACTION]: spy,
159 | },
160 | });
161 | const C = createSink(function(props) {
162 | props.onSubmit('xxx');
163 | });
164 | const Wrapper = mapActionCreatorsToProps({
165 | onSubmit: actionCreator,
166 | })(C);
167 |
168 | mount(Wrapper, {
169 | install: (Vue) => Vue.prototype.$store = store,
170 | });
171 |
172 | t.true(spy.called);
173 | t.is(spy.lastCall.args[1].payload.foo, 'xxx');
174 | });
175 |
--------------------------------------------------------------------------------
/packages/vuex-compose/spec/getters.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import sinon from 'sinon';
3 | import { mount, mockStore } from 'vuenit';
4 | import { createSink, compose } from 'vue-compose';
5 |
6 | import {
7 | mapGettersToProps,
8 | mapGettersToComputed,
9 | } from 'vuex-compose';
10 |
11 | test('maps getters to props', t => {
12 | const store = mockStore({
13 | state: {
14 | count: 2,
15 | },
16 | getters: {
17 | double: (state) => state.count * 2,
18 | },
19 | });
20 | const C = createSink((props) => {
21 | t.is(props.double, 4);
22 | });
23 | const Wrapper = mapGettersToProps([ 'double' ])(C);
24 |
25 | mount(Wrapper, {
26 | install: (Vue) => Vue.prototype.$store = store,
27 | });
28 | });
29 |
30 | test('maps getter factories to props', t => {
31 | const store = mockStore({
32 | state: {
33 | count: 2,
34 | },
35 | getters: {
36 | MULTIPLY: (state) => (x) => state.count * x,
37 | },
38 | });
39 | const C = createSink((props) => {
40 | t.is(typeof props.multiply, 'function');
41 | t.is(props.multiply(3), 6);
42 | });
43 |
44 | const enhance = compose(
45 | mapGettersToProps({
46 | multiply: 'MULTIPLY',
47 | }),
48 | );
49 |
50 | const Wrapper = enhance(C);
51 |
52 | mount(Wrapper, {
53 | install: (Vue) => Vue.prototype.$store = store,
54 | });
55 | });
56 |
57 | test('maps getters to computed values', t => {
58 | const store = mockStore({
59 | state: {
60 | count: 2,
61 | },
62 | getters: {
63 | double: (state) => state.count * 2,
64 | },
65 | });
66 | const C = createSink(function(props){
67 | t.is(props.double, undefined);
68 | t.is(this.double, 4);
69 | });
70 | const Wrapper = mapGettersToComputed([ 'double' ])(C);
71 |
72 | mount(Wrapper, {
73 | install: (Vue) => Vue.prototype.$store = store,
74 | });
75 | });
76 |
77 | test('maps getters from a namespaced module', t => {
78 | const store = mockStore({
79 | foo: {
80 | bah: {
81 | state: {
82 | count: 2,
83 | },
84 | getters: {
85 | double: (state) => state.count * 2,
86 | },
87 | }
88 | }
89 | });
90 | const C = createSink((props) => {
91 | t.is(props.double, 4);
92 | });
93 | const Wrapper = mapGettersToProps('foo/bah', [ 'double' ])(C);
94 |
95 | mount(Wrapper, {
96 | install: (Vue) => Vue.prototype.$store = store,
97 | });
98 | });
99 |
--------------------------------------------------------------------------------
/packages/vuex-compose/spec/hooks.js:
--------------------------------------------------------------------------------
1 | const hooks = require('require-extension-hooks');
2 | const browserEnv = require('browser-env');
3 | const moduleAlias = require('module-alias');
4 | const path = require('path');
5 | browserEnv();
6 | // moduleAlias.addAlias('vue', 'vue/dist/vue.runtime.min.js');
7 | moduleAlias.addAlias('vue', 'vue/dist/vue.runtime.js');
8 | moduleAlias.addAlias('vuex-compose', path.join(__dirname, '../src'));
9 |
10 | hooks('js').exclude('**/node_modules/**/*.*').plugin('babel');
11 |
--------------------------------------------------------------------------------
/packages/vuex-compose/spec/mapStateToProps.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import sinon from 'sinon';
3 | import { mount, mockStore } from 'vuenit';
4 | import { createSink } from 'vue-compose';
5 |
6 | import { mapStateToProps } from 'vuex-compose';
7 |
8 | test('it maps an array of keys to props', (t) => {
9 | const C = createSink((props) => {
10 | t.is(props.forename, 'John');
11 | t.is(props.surname, 'Doe');
12 | });
13 | const Wrapper = mapStateToProps(['forename', 'surname'])(C);
14 | const store = mockStore({
15 | state: {
16 | forename: 'John',
17 | surname: 'Doe',
18 | },
19 | });
20 |
21 | mount(Wrapper, {
22 | install: (Vue) => {
23 | Vue.prototype.$store = store;
24 | },
25 | });
26 | });
27 |
28 | test('it maps an object of key/values to props', (t) => {
29 | const C = createSink((props) => {
30 | t.is(props.firstName, 'John');
31 | t.is(props.lastName, 'Doe');
32 | });
33 | const Wrapper = mapStateToProps({
34 | firstName: 'forename',
35 | lastName: 'surname',
36 | })(C);
37 | const store = mockStore({
38 | state: {
39 | forename: 'John',
40 | surname: 'Doe',
41 | },
42 | });
43 |
44 | mount(Wrapper, {
45 | install: (Vue) => {
46 | Vue.prototype.$store = store;
47 | },
48 | });
49 | });
50 |
51 | test('it maps an object of key/functions to props', (t) => {
52 | const C = createSink((props) => {
53 | t.is(props.firstName, 'John');
54 | t.is(props.lastName, 'Doe');
55 | });
56 | const Wrapper = mapStateToProps({
57 | firstName: (state) => state.forename,
58 | lastName: (state) => state.surname,
59 | })(C);
60 | const store = mockStore({
61 | state: {
62 | forename: 'John',
63 | surname: 'Doe',
64 | },
65 | });
66 |
67 | mount(Wrapper, {
68 | install: (Vue) => {
69 | Vue.prototype.$store = store;
70 | },
71 | });
72 | });
73 |
74 | test('it throws if an invalid value provided', (t) => {
75 | const C = createSink(() => {});
76 | t.throws(() => mapStateToProps({
77 | firstName: 'forename',
78 | lastName: 9,
79 | })(C));
80 | });
81 |
82 | test('it maps a namespaced module to props', t => {
83 | const C = createSink((props) => {
84 | t.is(props.forename, 'John');
85 | t.is(props.surname, 'Doe');
86 | });
87 | const Wrapper = mapStateToProps('foo', [ 'forename', 'surname' ])(C);
88 | const store = mockStore({
89 | modules: {
90 | foo: {
91 | state: {
92 | forename: 'John',
93 | surname: 'Doe',
94 | },
95 | },
96 | },
97 | });
98 |
99 | mount(Wrapper, {
100 | install: (Vue) => {
101 | Vue.prototype.$store = store;
102 | },
103 | });
104 | });
105 |
--------------------------------------------------------------------------------
/packages/vuex-compose/spec/mutations.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import sinon from 'sinon';
3 | import { mount, mockStore } from 'vuenit';
4 | import { createSink } from 'vue-compose';
5 |
6 | import {
7 | mapMutationsToHandlers,
8 | } from 'vuex-compose';
9 |
10 | test('maps mutations to handlers', t => {
11 | const spy = sinon.spy();
12 | const store = mockStore({
13 | mutations: {
14 | FOO: spy,
15 | },
16 | });
17 | const C = createSink(function () {
18 | this.$emit('submit', 'xxx');
19 | });
20 | const Wrapper = mapMutationsToHandlers({
21 | submit: 'FOO',
22 | })(C);
23 |
24 | mount(Wrapper, {
25 | install: (Vue) => Vue.prototype.$store = store,
26 | });
27 |
28 | t.true(spy.called);
29 | t.is(spy.lastCall.args[1], 'xxx');
30 | });
31 |
--------------------------------------------------------------------------------
/packages/vuex-compose/spec/registerModule.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import { mount, mockStore } from 'vuenit';
3 | import Vue from 'vue';
4 | import vuex from 'vuex';
5 | import { createSink } from 'vue-compose';
6 |
7 | import { registerModule } from 'vuex-compose';
8 |
9 | Vue.use(vuex);
10 |
11 | test('registers a module', (t) => {
12 | const store = mockStore();
13 | const C = createSink(() => {});
14 | const Wrapper = registerModule(
15 | 'foobah',
16 | {
17 | state: {
18 | foo: 'bah',
19 | },
20 | },
21 | )(C);
22 |
23 | mount(Wrapper, {
24 | install: (Vue) => Vue.prototype.$store = store,
25 | });
26 |
27 | t.is(store.state.foobah.foo, 'bah');
28 | });
29 |
30 | test('does not overwrite an existing module', (t) => {
31 | const store = mockStore({
32 | modules: {
33 | foobah: {
34 | foo: 'foo',
35 | },
36 | },
37 | });
38 | const C = createSink(() => {});
39 | const Wrapper = registerModule(
40 | 'foobah',
41 | {
42 | state: {
43 | foo: 'bah',
44 | },
45 | },
46 | )(C);
47 |
48 | mount(Wrapper, {
49 | install: (Vue) => Vue.prototype.$store = store,
50 | });
51 |
52 | t.is(store.state.foobah.foo, 'foo');
53 | });
54 |
55 | test('registers a deep module', (t) => {
56 | const store = new vuex.Store({});
57 | const C = createSink(() => {});
58 | const Wrapper = registerModule(
59 | 'foo/bah/baz',
60 | {
61 | state: {
62 | foo: 'bah',
63 | },
64 | },
65 | )(C);
66 |
67 | mount(Wrapper, {
68 | install: (Vue) => Vue.prototype.$store = store,
69 | });
70 |
71 | t.is(store.state.foo.bah.baz.foo, 'bah');
72 | });
73 |
--------------------------------------------------------------------------------
/packages/vuex-compose/src/actions.js:
--------------------------------------------------------------------------------
1 | import { mapActions } from 'vuex';
2 | import {
3 | compose,
4 | withHandlers,
5 | withMethods,
6 | withProps,
7 | } from 'vue-compose';
8 | import {
9 | createMapper,
10 | } from './utils';
11 |
12 | const mapper = createMapper(mapActions);
13 |
14 | export const mapActionsToHandlers = mapper(withHandlers);
15 |
16 | export const mapActionsToMethods = mapper(withMethods);
17 |
18 | // export const mapActionsToProps = mapper(withProps);
19 | export const mapActionsToProps = (namespace, map) => compose(
20 | mapActionsToMethods(namespace, map),
21 | withProps(function () {
22 | const props = {};
23 | Object.keys(this.$options.methods).forEach((key) => {
24 | props[key] = this[key];
25 | });
26 | return props;
27 | }),
28 | );
29 |
30 | const creatorMapper = (method) => (creators) => {
31 | const handlers = {};
32 |
33 | Object.keys(creators).forEach((key) => {
34 | const creator = creators[key];
35 | handlers[key] = function () {
36 | const args = Array.prototype.slice.call(arguments);
37 | const action = creator.apply(null, args);
38 |
39 | return this.$store.dispatch(action);
40 | };
41 | });
42 |
43 | return method(handlers);
44 | };
45 |
46 | export const mapActionCreatorsToHandlers = creatorMapper(withHandlers);
47 |
48 | export const mapActionCreatorsToMethods = creatorMapper(withMethods);
49 |
50 | export const mapActionCreatorsToProps = (creators) => compose(
51 | mapActionCreatorsToMethods(creators),
52 | withProps(function () {
53 | const props = {};
54 | Object.keys(this.$options.methods).forEach((key) => {
55 | props[key] = this[key];
56 | });
57 | return props;
58 | }),
59 | );
60 |
--------------------------------------------------------------------------------
/packages/vuex-compose/src/connect.js:
--------------------------------------------------------------------------------
1 | import withState from './mapStateToProps';
2 | import {
3 | mapActionsToHandlers,
4 | mapActionCreatorsToHandlers,
5 | } from './actions';
6 | import {
7 | mapGettersToProps,
8 | } from './getters';
9 | import {
10 | compose,
11 | } from 'vue-compose';
12 | import {
13 | containsFunctions,
14 | } from './utils';
15 |
16 | const connect = (
17 | namespace,
18 | stateFn,
19 | dispatchFn,
20 | getterFn,
21 | ) => {
22 | if (typeof namespace !== 'string') {
23 | getterFn = dispatchFn;
24 | dispatchFn = stateFn;
25 | stateFn = namespace;
26 | namespace = '';
27 | }
28 |
29 | const args = [];
30 |
31 | if (stateFn != null) {
32 | args.push(withState(namespace, stateFn));
33 | }
34 | if (dispatchFn != null) {
35 | if (containsFunctions(dispatchFn)) {
36 | args.push(mapActionCreatorsToHandlers(namespace, dispatchFn));
37 | } else {
38 | args.push(mapActionsToHandlers(namespace, dispatchFn));
39 | }
40 | }
41 | if (getterFn != null) {
42 | args.push(mapGettersToProps(namespace, getterFn));
43 | }
44 |
45 | return compose.apply(null, args);
46 | };
47 |
48 | export default connect;
49 |
--------------------------------------------------------------------------------
/packages/vuex-compose/src/getters.js:
--------------------------------------------------------------------------------
1 | import { mapGetters } from 'vuex';
2 | import {
3 | withProps,
4 | withComputed,
5 | } from 'vue-compose';
6 | import {
7 | createMapper,
8 | } from './utils';
9 |
10 | const mapper = createMapper(mapGetters);
11 |
12 | export const mapGettersToProps = mapper(withProps);
13 |
14 | export const mapGettersToComputed = mapper(withComputed);
15 |
--------------------------------------------------------------------------------
/packages/vuex-compose/src/index.js:
--------------------------------------------------------------------------------
1 | import { compose } from 'vue-compose';
2 |
3 | import registerModule from './registerModule';
4 | import mapStateToProps from './mapStateToProps';
5 | import {
6 | mapGettersToProps,
7 | mapGettersToComputed,
8 | } from './getters';
9 | import {
10 | mapMutationsToHandlers,
11 | } from './mutations';
12 | import {
13 | mapActionsToHandlers,
14 | mapActionsToMethods,
15 | mapActionsToProps,
16 | mapActionCreatorsToProps,
17 | mapActionCreatorsToMethods,
18 | mapActionCreatorsToHandlers,
19 | } from './actions';
20 | import connect from './connect';
21 |
22 | export {
23 | compose,
24 | registerModule,
25 | mapStateToProps as withState,
26 | mapStateToProps,
27 | mapGettersToProps as withGetters,
28 | mapGettersToProps,
29 | mapGettersToComputed,
30 | mapMutationsToHandlers as withMutations,
31 | mapMutationsToHandlers,
32 | mapActionsToHandlers as withActions,
33 | mapActionsToHandlers,
34 | mapActionsToMethods,
35 | mapActionsToProps,
36 | mapActionCreatorsToHandlers as withActionCreators,
37 | mapActionCreatorsToProps,
38 | mapActionCreatorsToMethods,
39 | mapActionCreatorsToHandlers,
40 | connect,
41 | };
42 |
--------------------------------------------------------------------------------
/packages/vuex-compose/src/mapStateToProps.js:
--------------------------------------------------------------------------------
1 | import { withProps } from 'vue-compose';
2 |
3 | const mapStateToProps = (namespace, map) => {
4 | if (typeof namespace !== 'string') {
5 | map = namespace;
6 | namespace = '';
7 | }
8 | namespace = namespace ? namespace.split('/') : [];
9 |
10 | const getState = (store) => {
11 | return namespace.reduce((state, n) => {
12 | return state && state[n];
13 | }, store.state);
14 | };
15 |
16 | const mapped = {};
17 |
18 | if (Array.isArray(map)) {
19 | map.forEach(key => {
20 | mapped[key] = function(){
21 | const state = getState(this.$store);
22 | return state[key];
23 | };
24 | });
25 | } else {
26 | Object.keys(map).forEach(key => {
27 | const value = map[key];
28 |
29 | switch (typeof value){
30 | case 'string':
31 | mapped[key] = function () {
32 | const state = getState(this.$store);
33 | return state[value];
34 | };
35 | break;
36 | case 'function':
37 | mapped[key] = function () {
38 | const state = getState(this.$store);
39 | return value.call(this, state);
40 | };
41 | break;
42 | default:
43 | throw new Error(`Invalid type ${Object.prototype.toString.call(value)} for withState`);
44 | }
45 | });
46 | }
47 |
48 | return withProps(mapped);
49 | };
50 |
51 | export default mapStateToProps;
52 |
--------------------------------------------------------------------------------
/packages/vuex-compose/src/mutations.js:
--------------------------------------------------------------------------------
1 | import { mapMutations } from 'vuex';
2 | import {
3 | withHandlers
4 | } from 'vue-compose';
5 | import {
6 | createMapper,
7 | } from './utils';
8 |
9 | const mapper = createMapper(mapMutations);
10 |
11 | export const mapMutationsToHandlers = mapper(withHandlers);
12 |
--------------------------------------------------------------------------------
/packages/vuex-compose/src/registerModule.js:
--------------------------------------------------------------------------------
1 | import { withHooks } from 'vue-compose';
2 |
3 | export default (namespace, store) => {
4 | if (typeof namespace === 'string') {
5 | namespace = namespace.split('/');
6 | }
7 | return withHooks({
8 | created () {
9 | let state = this.$store.state;
10 | let keys = [];
11 |
12 | namespace.forEach((key, i) => {
13 | keys.push(key);
14 | if (!state[key]) {
15 | if (i === namespace.length - 1) {
16 | this.$store.registerModule(keys, store);
17 | } else {
18 | this.$store.registerModule(keys, {});
19 | }
20 | }
21 | state = state[key];
22 | });
23 | },
24 | });
25 | };
26 |
--------------------------------------------------------------------------------
/packages/vuex-compose/src/utils.js:
--------------------------------------------------------------------------------
1 | export const createMapper = (vuexMethod) => (composer) => (namespace, map) => {
2 | const method = vuexMethod(namespace, map);
3 |
4 | return composer(
5 | method,
6 | );
7 | };
8 |
9 | export const containsFunctions = (obj) => {
10 | if (typeof obj !== 'object') {
11 | return false;
12 | }
13 | const keys = Object.keys(obj);
14 | const t = typeof obj[keys[0]];
15 |
16 | return t === 'function';
17 | };
18 |
--------------------------------------------------------------------------------
/travis/login.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | echo -e "$USER\n$PASS\n$EMAIL" | npm adduser
4 |
--------------------------------------------------------------------------------
/travis/push.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | setup_git() {
4 | git config --global user.email "travis@travis-ci.org"
5 | git config --global user.name "Travis CI"
6 | }
7 |
8 | commit_changelogs() {
9 | git add **/CHANGELOG.md
10 | git commit --message "Travis build: $TRAVIS_BUILD_NUMBER"
11 | }
12 |
13 | upload_files() {
14 | git remote add origin-master https://${GH_TOKEN}@github.com/jackmellis/vue-hoc.git > /dev/null 2>&1
15 | git push --quiet --set-upstream origin-master master
16 | git push --quiet --set-upstream origin-master master --tags
17 | }
18 |
19 | setup_git
20 | commit_changelogs
21 | upload_files
22 |
--------------------------------------------------------------------------------