├── .browserslistrc
├── .circleci
└── config.yml
├── .editorconfig
├── .eslintrc.js
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── logo.png
├── .gitignore
├── .huskyrc.json
├── .npmignore
├── .storybook
├── main.js
├── manager.js
└── theme.js
├── LICENSE
├── README.md
├── babel.config.js
├── config
└── storybook
│ ├── addons.js
│ ├── config.js
│ └── preview-head.html
├── jest.config.js
├── package.json
├── postcss.config.js
├── src
└── components
│ └── VueImageKit.vue
├── stories
└── index.stories.js
├── tests
└── unit
│ ├── .eslintrc.js
│ ├── __snapshots__
│ └── vue-image-kit.spec.js.snap
│ └── vue-image-kit.spec.js
├── vue-image-kit.js
└── yarn.lock
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not ie <= 8
4 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 |
3 | defaults: &defaults
4 | docker:
5 | - image: circleci/node
6 |
7 | working_directory: ~/app
8 |
9 | orbs:
10 | codecov: codecov/codecov@1.0.4
11 |
12 | jobs:
13 | commitlint:
14 | docker:
15 | - image: williamlauze/circleci-commitlint:latest
16 | working_directory: ~/app
17 |
18 | steps:
19 | - checkout
20 | - run:
21 | name: Lint commit messages
22 | command: /bin/sh /.scripts/commitlint_range.sh
23 |
24 | checkout:
25 | <<: *defaults
26 |
27 | steps:
28 | - restore_cache:
29 | name: Restore Repository Cache
30 | keys:
31 | - repo-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }}
32 | - repo-{{ .Branch }}
33 | - repo-master
34 | - repo-
35 |
36 | - checkout
37 |
38 | - save_cache:
39 | name: Save Repository Cache
40 | key: repo-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }}
41 | paths:
42 | - .
43 |
44 | - persist_to_workspace:
45 | root: .
46 | paths:
47 | - .
48 |
49 | install-dependencies:
50 | <<: *defaults
51 |
52 | steps:
53 | - attach_workspace:
54 | at: .
55 |
56 | - restore_cache:
57 | name: Restore yarn Package Cache
58 | keys:
59 | - yarn-{{ checksum "yarn.lock" }}
60 | - yarn-
61 |
62 | - run:
63 | name: Install Dependencies
64 | command: yarn install
65 |
66 | - save_cache:
67 | name: Save yarn Package Cache
68 | key: yarn-{{ checksum "yarn.lock" }}
69 | paths:
70 | - node_modules
71 |
72 | - persist_to_workspace:
73 | root: .
74 | paths:
75 | - node_modules
76 |
77 | lint:
78 | <<: *defaults
79 |
80 | steps:
81 | - attach_workspace:
82 | at: .
83 |
84 | - run:
85 | name: Add Yarn Binary Folder To $PATH
86 | command: export PATH="$PATH:`yarn global bin`"
87 |
88 | - run:
89 | name: Lint JS
90 | command: yarn lint
91 |
92 | test:
93 | <<: *defaults
94 |
95 | steps:
96 | - attach_workspace:
97 | at: .
98 |
99 | - run:
100 | name: Add Yarn Binary Folder To $PATH
101 | command: export PATH="$PATH:`yarn global bin`"
102 |
103 | - run:
104 | name: Run tests
105 | command: yarn test:unit
106 |
107 | coverage:
108 | <<: *defaults
109 |
110 | steps:
111 | - attach_workspace:
112 | at: .
113 |
114 | - run:
115 | name: Add Yarn Binary Folder To $PATH
116 | command: export PATH="$PATH:`yarn global bin`"
117 |
118 | - run:
119 | name: Generate and upload coverage report
120 | command: yarn test:unit --coverage --coverageReporters=json --collectCoverage=true --coverageDirectory=/home/circleci/app/coverage
121 |
122 | - store_test_results:
123 | path: ~/app/coverage
124 |
125 | - store_artifacts:
126 | path: ~/app/coverage
127 | destination: ~/app/coverage
128 |
129 | - codecov/upload:
130 | token: 84850ad5-b40a-4ebb-975b-ee2c5b99d28e
131 | file: ~/app/coverage/*.json
132 | flags: frontend
133 |
134 | workflows:
135 | version: 2
136 |
137 | main:
138 | jobs:
139 | - checkout
140 | - commitlint:
141 | filters:
142 | branches:
143 | ignore: master
144 | - install-dependencies:
145 | requires:
146 | - checkout
147 | - test:
148 | requires:
149 | - install-dependencies
150 | - coverage:
151 | requires:
152 | - install-dependencies
153 | - lint:
154 | requires:
155 | - install-dependencies
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.{js,jsx,ts,tsx,vue}]
2 | indent_style = space
3 | indent_size = 2
4 | trim_trailing_whitespace = true
5 | insert_final_newline = true
6 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true
5 | },
6 | extends: [
7 | 'plugin:vue/essential',
8 | '@vue/standard'
9 | ],
10 | rules: {
11 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
12 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
13 | },
14 | parserOptions: {
15 | parser: 'babel-eslint'
16 | },
17 | overrides: [
18 | {
19 | files: [
20 | '**/tests/unit/*.spec.{j,t}s?(x)'
21 | ],
22 | env: {
23 | jest: true
24 | }
25 | }
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | ko_fi: guastallaigor
4 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 |
5 | ---
6 |
7 |
8 |
9 |
10 | **Describe the bug**
11 |
12 |
13 | **To Reproduce**
14 |
21 |
22 | **Expected behavior**
23 |
24 |
25 | **Screenshots**
26 |
27 |
28 | **Environment:**
29 | - OS:
30 | - Browser:
31 | - Build environment (i.e. NodeJS):
32 |
33 | **Suggestion(s) for fixing this issue**
34 |
35 |
36 | **Additional context**
37 |
38 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 |
5 | ---
6 |
7 |
8 |
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 |
12 |
13 | **Describe the solution you'd like**
14 |
15 |
16 | **Describe alternatives you've considered**
17 |
18 |
19 | **Additional context**
20 |
21 |
--------------------------------------------------------------------------------
/.github/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guastallaigor/vue-image-kit/53d1645b0cd9aeb97b48a34e44dcda4e8fdb2a4f/.github/logo.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # Editor directories and files
15 | .idea
16 | .vscode
17 | *.suo
18 | *.ntvs*
19 | *.njsproj
20 | *.sln
21 | *.sw*
22 |
23 | # Coverage directory used by tools like istanbul
24 | coverage
25 |
26 | # Static
27 | storybook-static
28 |
--------------------------------------------------------------------------------
/.huskyrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "hooks": {
3 | "pre-push": [
4 | "npm run lint && npm run test:unit"
5 | ]
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # User & editor settings
2 | .editorconfig
3 | .huskyrc
4 | .vscode
5 | jest.config.js
6 | postcss.config.js
7 |
8 | # Build files
9 | .circleci
10 | .github
11 | .storybook
12 | commitlint.config.js
13 | node_modules
14 |
15 | # Documentation files
16 | storybook-static
17 | config
18 | stories
19 | tests
20 | coverage
21 | .eslintrc.js
22 | .huskyrc.json
--------------------------------------------------------------------------------
/.storybook/main.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | stories: ['../stories/**/*.stories.js'],
3 | logLevel: 'debug',
4 | addons: [
5 | '@storybook/addon-controls',
6 | '@storybook/addon-docs',
7 | '@storybook/addon-a11y'
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/.storybook/manager.js:
--------------------------------------------------------------------------------
1 | import { addons } from '@storybook/addons'
2 | import theme from './theme'
3 |
4 | addons.setConfig({ theme })
5 |
--------------------------------------------------------------------------------
/.storybook/theme.js:
--------------------------------------------------------------------------------
1 | import { create } from '@storybook/theming/create'
2 |
3 | export default create({
4 | base: 'light',
5 |
6 | colorPrimary: '#41b883',
7 | colorSecondary: '#060a0e',
8 |
9 | // UI
10 | appBg: '#152232',
11 | appContentBg: '#0b1622',
12 | appBorderColor: '#CBD5E0',
13 | appBorderRadius: 4,
14 |
15 | // Typography
16 | fontBase: '-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", "Open Sans", sans-serif',
17 | fontCode: 'monospace',
18 |
19 | // Text colors
20 | textColor: '#94A3B8',
21 | textInverseColor: 'black',
22 |
23 | // Toolbar default and active colors
24 | barTextColor: 'silver',
25 | barSelectedColor: '#FFFFFF',
26 | barBg: '#152232',
27 |
28 | // Form colors
29 | inputBg: '#152232',
30 | inputBorder: '#152232',
31 | inputTextColor: '#FFFFFF',
32 | inputBorderRadius: 4,
33 |
34 | brandTitle: 'Vue Image Kit',
35 | brandUrl: 'https://github.com/guastallaigor/vue-image-kit',
36 | brandImage: 'https://raw.githubusercontent.com/guastallaigor/vue-image-kit/master/.github/logo.png'
37 | })
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Igor Guastalla de Lima
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |

3 |
Vue Image Kit
4 |
5 |
6 | Vue.js Image Kit Component with Lazy Load built in and Responsive Images
7 |
8 |
9 | The inspiration comes from this and a talk from @derevandal in @femug
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | > **Note:**
22 | > This is an unofficial project.
23 | > I do not work or am I affiliated with Image Kit
24 |
25 | ## Demo
26 |
27 | [](https://codesandbox.io/s/vue-image-kit-keeh1)
28 |
29 | ## Storybook
30 |
31 | Go to https://vue-image-kit.netlify.app
32 |
33 | ## How does it work
34 |
35 | This component uses the [Image Kit Real-time URL-based image transformation](https://imagekit.io/features/image-resize-smart-crop-responsive-dpr-client-hints), so you will need to have your images over [Image Kit](https://imagekit.io/) for it to work
36 |
37 | For more informations about [Image Kit](https://imagekit.io/), consult their website
38 |
39 | ## How to install
40 |
41 | ### npm
42 |
43 | ```bash
44 | $ npm install vue-image-kit --save
45 | ```
46 |
47 | ### yarn (recommended)
48 |
49 | ```bash
50 | $ yarn add vue-image-kit
51 | ```
52 |
53 | ## Quick start
54 |
55 | ### Vue.js
56 |
57 | You can import in your `main.js` file
58 |
59 | ```js
60 | import Vue from "vue";
61 | import VueImageKit from "vue-image-kit";
62 |
63 | Vue.use(VueImageKit);
64 | ```
65 |
66 | Or locally in any component
67 |
68 | ```js
69 | import { VueImageKit } from "vue-image-kit";
70 | // In v0.2+ you don't need the brackets above
71 |
72 | export default {
73 | components: {
74 | VueImageKit,
75 | },
76 | };
77 | ```
78 |
79 | ### Nuxt.js
80 |
81 | You can import as a Nuxt.js plugin
82 |
83 | `~/plugins/vue-image-kit.js`
84 |
85 | ```js
86 | import Vue from "vue";
87 | import VueImageKit from "vue-image-kit";
88 |
89 | Vue.use(VueImageKit);
90 | ```
91 |
92 | and then import it in your `nuxt.config.js` file
93 |
94 | ```js
95 | plugins: [{ src: "~/plugins/vue-image-kit", mode: "client" }];
96 | ```
97 |
98 | ## Basic usage
99 |
100 | ```html
101 |
102 |
109 |
110 | ```
111 |
112 | ## Props
113 |
114 | | Property name | Type | Default | Required | Description |
115 | | --------------- | ------- | --------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
116 | | hash | String | null | true | Images hash. Example: Take this image -> https://ik.imagekit.io/6xhf1gnexgdgk/lion_BllLvaqVn.jpg, the hash is '6xhf1gnexgdgk' |
117 | | src | String | null | true | Images source. Example: Take this image -> https://ik.imagekit.io/6xhf1gnexgdgk/lion_BllLvaqVn.jpg, the source is 'lion_BllLvaqVn.jpg' |
118 | | placeholder | String | '' | false | Images placeholder. Here you can pass a link |
119 | | backgroundColor | String | '' | false | Background color of the images placeholder |
120 | | srcset | Array | [320, 480, 800] | false | Array of numbers that will define the images srcset attribute. Each number correspond to one of the images width |
121 | | sizes | Array | [] | false | Array of numbers that will define the images sizes attribute. Each number correspond to one of the images max-width. Empty by default, which gets each of the images srcset prop |
122 | | defaultSize | Number | 1080 | true | Images default size. Must be larger than the largest srcset and sizes |
123 | | customTransform | String | '' | false | Use this to append any extra image kit transform that you want |
124 | | width | Number | null | false | Images width. Width number in pixels. It will be set with inline style |
125 | | height | Number | null | false | Images height. Height number in pixels. It will be set with inline style |
126 | | alt | String | '' | false | Images alt attribute |
127 | | lazyLoad | Boolean | true | false | If you don't want to use the built in lazy load, you can set this to false, then the image will not be lazy loaded, and you can setup your own lazy load |
128 |
129 | ## Development
130 |
131 | [](https://github.com/guastallaigor/vue-image-kit/issues)
132 |
133 | > **Note**: Contributions are very welcomed, however is very important to open a new issue using the issue template **before** you start working on anything, so we can discuss it before hand
134 |
135 | Fork the project and enter this commands in your terminal
136 |
137 | ```sh
138 | git clone https://github.com/YOUR_GITHUB_USERNAME/vue-image-kit.git
139 | cd vue-image-kit
140 | yarn
141 | ```
142 |
143 | ### Storybook
144 |
145 | For visual testing, this project contains storybook which you can run by doing the next command
146 |
147 | ```sh
148 | $ yarn storybook
149 | ```
150 |
151 | ### Jest
152 |
153 | Before making the PR, if you changed something that needs to be tested, please make the tests inside the `tests/unit` folder
154 |
155 | To run the tests, you can use the next command
156 |
157 | ```sh
158 | $ yarn test:unit
159 | ```
160 |
161 | ### Commitlint
162 |
163 | This project follows the [commitlint](https://github.com/conventional-changelog/commitlint) guidelines, with minor changes
164 |
165 | You can commit using `npm run commit` to help you with that
166 |
167 | There's a `pre-push` hook that runs all the unit tests before you can push it
168 |
169 | If an error occurs, you can use the `npm run commit:retry` command that runs the previous `npm run commit` that you already filled
170 |
171 | [](https://ko-fi.com/C1C63QCB8)
172 |
173 | ## License
174 |
175 | MIT © [guastallaigor](https://github.com/guastallaigor/vue-image-kit/blob/master/LICENSE)
176 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['@vue/cli-plugin-babel/preset']
3 | }
4 |
--------------------------------------------------------------------------------
/config/storybook/addons.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-extraneous-dependencies */
2 | import '@storybook/addon-knobs/register'
3 | import '@storybook/addon-links/register'
4 | import '@storybook/addon-notes/register'
5 | import '@storybook/addon-backgrounds/register'
6 |
--------------------------------------------------------------------------------
/config/storybook/config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-extraneous-dependencies */
2 | import { configure } from '@storybook/vue'
3 |
4 | const req = require.context('../../stories', true, /.stories.js$/)
5 |
6 | function loadStories () {
7 | req.keys().forEach(filename => req(filename))
8 | }
9 |
10 | configure(loadStories, module)
11 |
--------------------------------------------------------------------------------
/config/storybook/preview-head.html:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: '@vue/cli-plugin-unit-jest',
3 | coverageDirectory: './coverage/',
4 | collectCoverage: true
5 | }
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-image-kit",
3 | "version": "0.2.2",
4 | "private": false,
5 | "description": "Vue.js Image Kit Component with Lazy Load built in and Responsive Images",
6 | "homepage": "https://github.com/guastallaigor/vue-image-kit#readme",
7 | "author": "Igor Guastalla de Lima",
8 | "scripts": {
9 | "lint": "vue-cli-service lint",
10 | "storybook": "start-storybook -p 6006 -s public",
11 | "build-storybook": "build-storybook",
12 | "test:unit": "vue-cli-service test:unit",
13 | "test:watch": "vue-cli-service test:unit --watch",
14 | "commit": "git-cz",
15 | "commit:retry": "git-cz --retry",
16 | "commitmsg": "commitlint -e"
17 | },
18 | "dependencies": {
19 | "vue": "^2.6.12"
20 | },
21 | "devDependencies": {
22 | "@commitlint/cli": "^11.0.0",
23 | "@commitlint/config-conventional": "^11.0.0",
24 | "@storybook/addon-a11y": "^6.1.6",
25 | "@storybook/addon-actions": "^6.1.6",
26 | "@storybook/addon-backgrounds": "^6.1.6",
27 | "@storybook/addon-controls": "^6.1.6",
28 | "@storybook/addon-docs": "^6.1.6",
29 | "@storybook/addon-links": "^6.1.6",
30 | "@storybook/addons": "^6.1.6",
31 | "@storybook/theming": "^6.1.6",
32 | "@storybook/vue": "^6.1.6",
33 | "@vue/cli-plugin-babel": "^4.5.9",
34 | "@vue/cli-plugin-eslint": "^4.5.9",
35 | "@vue/cli-plugin-unit-jest": "^4.5.9",
36 | "@vue/cli-service": "^4.5.9",
37 | "@vue/eslint-config-standard": "^5.1.2",
38 | "@vue/test-utils": "^1.1.1",
39 | "babel-eslint": "^10.1.0",
40 | "commitizen": "^4.2.2",
41 | "core-js": "3.8.0",
42 | "eslint": "^7.14.0",
43 | "eslint-plugin-import": "^2.22.1",
44 | "eslint-plugin-node": "^11.1.0",
45 | "eslint-plugin-promise": "^4.2.1",
46 | "eslint-plugin-standard": "^5.0.0",
47 | "eslint-plugin-vue": "^5.0.0s",
48 | "husky": "^4.3.0",
49 | "jest-canvas-mock": "^2.3.0",
50 | "vue-cli-plugin-storybook": "^2.0.0",
51 | "vue-template-compiler": "^2.6.12"
52 | },
53 | "bugs": {
54 | "url": "https://github.com/guastallaigor/vue-image-kit/issues"
55 | },
56 | "files": [
57 | "src/*",
58 | "*.json",
59 | "*.js"
60 | ],
61 | "keywords": [
62 | "vue",
63 | "vuejs",
64 | "image",
65 | "lazy-load",
66 | "kit",
67 | "component",
68 | "image-kit"
69 | ],
70 | "license": "MIT",
71 | "main": "./vue-image-kit.js",
72 | "repository": {
73 | "type": "git",
74 | "url": "https://github.com:guastallaigor/vue-image-kit.git"
75 | },
76 | "commitlint": {
77 | "extends": [
78 | "@commitlint/config-conventional"
79 | ],
80 | "rules": {
81 | "subject-case": [
82 | 0,
83 | "never",
84 | "sentence-case"
85 | ],
86 | "subject-full-stop": [
87 | 0,
88 | "never",
89 | "."
90 | ]
91 | }
92 | },
93 | "config": {
94 | "commitizen": {
95 | "path": "./node_modules/cz-conventional-changelog"
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | autoprefixer: {}
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/components/VueImageKit.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
![Placeholder]()
20 |
21 |
28 |
29 |
![]()
37 |
38 |
39 |
40 |
202 |
203 |
228 |
--------------------------------------------------------------------------------
/stories/index.stories.js:
--------------------------------------------------------------------------------
1 | import VueImageKit from '../src/components/VueImageKit.vue'
2 |
3 | export default {
4 | title: 'VueImageKit',
5 | component: VueImageKit,
6 | parameters: {
7 | a11y: {
8 | element: '#root',
9 | config: {},
10 | options: {},
11 | manual: true,
12 | },
13 | controls: { hideNoControlsWarning: true },
14 | docs: {
15 | inlineStories: true
16 | },
17 | backgrounds: [
18 | { name: 'Blue', value: 'blue' },
19 | { name: 'Green', value: 'green' },
20 | { name: 'Yellow', value: 'yellow' },
21 | { name: 'Orange', value: 'orange' },
22 | { name: 'Red', value: 'red' },
23 | { name: 'Purple', value: 'purple' },
24 | { name: 'Black', value: 'black' },
25 | { name: 'White', value: 'white', default: true }
26 | ]
27 | },
28 | argTypes: {
29 | hash: {
30 | control: 'text'
31 | },
32 | src: {
33 | control: 'text'
34 | },
35 | placeholder: {
36 | control: 'text'
37 | },
38 | backgroundColor: {
39 | control: 'text'
40 | },
41 | srcset: {
42 | control: 'array'
43 | },
44 | sizes: {
45 | control: 'array'
46 | },
47 | defaultSize: {
48 | control: 'number'
49 | },
50 | customTransform: {
51 | control: 'text'
52 | },
53 | width: {
54 | control: 'number'
55 | },
56 | height: {
57 | control: 'number'
58 | },
59 | alt: {
60 | control: 'text'
61 | },
62 | lazyLoad: {
63 | control: 'boolean'
64 | }
65 | }
66 | }
67 |
68 | export const DefaultComponent = () => ({
69 | components: { VueImageKit },
70 | props: {
71 | hash: {
72 | type: String,
73 | default: '6xhf1gnexgdgk'
74 | },
75 | src: {
76 | type: String,
77 | default: 'lion_BllLvaqVn.jpg'
78 | },
79 | placeholder: {
80 | type: String,
81 | default: ''
82 | },
83 | backgroundColor: {
84 | type: String,
85 | default: 'Background'
86 | },
87 | srcset: {
88 | type: Array,
89 | default: [320, 480, 800]
90 | },
91 | sizes: {
92 | type: Array,
93 | default: []
94 | },
95 | defaultSize: {
96 | type: Number,
97 | default: 1080
98 | },
99 | customTransform: {
100 | type: String,
101 | default: ''
102 | },
103 | width: {
104 | type: Number,
105 | default: 1400
106 | },
107 | height: {
108 | type: Number,
109 | default: 800
110 | },
111 | alt: {
112 | type: String,
113 | default: ''
114 | },
115 | lazyLoad: {
116 | type: Boolean,
117 | default: true
118 | }
119 | },
120 | template: ``
137 | })
138 |
--------------------------------------------------------------------------------
/tests/unit/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | jest: true
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/tests/unit/__snapshots__/vue-image-kit.spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`When I create the VueImageKit component should match snapshot 1`] = `
4 |
5 |
![]()
6 |
7 | `;
8 |
--------------------------------------------------------------------------------
/tests/unit/vue-image-kit.spec.js:
--------------------------------------------------------------------------------
1 | import { mount, shallowMount, createLocalVue } from '@vue/test-utils'
2 | import VueImageKit from '../../src/components/VueImageKit'
3 | import 'jest-canvas-mock'
4 |
5 | function setupIntersectionObserverMock ({
6 | observe = () => null,
7 | unobserve = () => null
8 | } = {}) {
9 | class IntersectionObserver {
10 | observe = observe
11 | unobserve = unobserve
12 | disconnect () {}
13 | }
14 | Object.defineProperty(
15 | window,
16 | 'IntersectionObserver',
17 | { writable: true, configurable: true, value: IntersectionObserver }
18 | )
19 | Object.defineProperty(
20 | global,
21 | 'IntersectionObserver',
22 | { writable: true, configurable: true, value: IntersectionObserver }
23 | )
24 | }
25 |
26 | describe('When I create the VueImageKit component', () => {
27 | const item = { hash: '6xhf1gnexgdgk', src: 'lion_BllLvaqVn.jpg' }
28 | let timeout
29 |
30 | beforeEach(() => {
31 | timeout = (ms) => new Promise(resolve => setTimeout(resolve, ms))
32 | setupIntersectionObserverMock()
33 | })
34 |
35 | const createComponent = (propsData = {}) => {
36 | return shallowMount(VueImageKit, { propsData })
37 | }
38 |
39 | it('should validate all props', () => {
40 | const consoleLog = console.error
41 | console.error = jest.fn()
42 | const wrapper = createComponent(item)
43 | const hash = wrapper.vm.$options.props.hash
44 | expect(hash.required).toBeTruthy()
45 | expect(hash.type).toBe(String)
46 | expect(hash.default).toBe(undefined)
47 | const src = wrapper.vm.$options.props.src
48 | expect(src.required).toBeTruthy()
49 | expect(src.type).toBe(String)
50 | expect(src.default).toBe(undefined)
51 | const placeholder = wrapper.vm.$options.props.placeholder
52 | expect(placeholder.required).toBeFalsy()
53 | expect(placeholder.type).toBe(String)
54 | expect(placeholder.default).toBe('')
55 | const backgroundColor = wrapper.vm.$options.props.backgroundColor
56 | expect(backgroundColor.required).toBeFalsy()
57 | expect(backgroundColor.type).toBe(String)
58 | expect(backgroundColor.default).toBe('')
59 | const srcset = wrapper.vm.$options.props.srcset
60 | expect(srcset.required).toBeFalsy()
61 | expect(srcset.type).toBe(Array)
62 | const defaultSrcset = { default: () => [320, 480, 800] }
63 | expect(JSON.stringify(srcset.default)).toEqual(JSON.stringify(defaultSrcset.default))
64 | const sizes = wrapper.vm.$options.props.sizes
65 | expect(sizes.required).toBeFalsy()
66 | expect(sizes.type).toBe(Array)
67 | expect(JSON.stringify(sizes.default)).toBeFalsy()
68 | const defaultSize = wrapper.vm.$options.props.defaultSize
69 | expect(defaultSize.required).toBeFalsy()
70 | expect(defaultSize.type).toBe(Number)
71 | expect(defaultSize.default).toBe(1080)
72 | const customTransform = wrapper.vm.$options.props.customTransform
73 | expect(customTransform.required).toBeFalsy()
74 | expect(customTransform.type).toBe(String)
75 | expect(customTransform.default).toBe('')
76 | const width = wrapper.vm.$options.props.width
77 | expect(width.required).toBeFalsy()
78 | expect(width.type).toBe(Number)
79 | expect(width.default).toBe(null)
80 | const height = wrapper.vm.$options.props.height
81 | expect(height.required).toBeFalsy()
82 | expect(height.type).toBe(Number)
83 | expect(height.default).toBe(null)
84 | const alt = wrapper.vm.$options.props.alt
85 | expect(alt.required).toBeFalsy()
86 | expect(alt.type).toBe(String)
87 | expect(alt.default).toBe('')
88 | const lazyLoad = wrapper.vm.$options.props.lazyLoad
89 | expect(lazyLoad.required).toBeFalsy()
90 | expect(lazyLoad.type).toBe(Boolean)
91 | expect(lazyLoad.default).toBe(true)
92 | const component = wrapper.find('.vue-image-kit')
93 | expect(component.exists()).toBe(true)
94 | console.error = consoleLog
95 | })
96 |
97 | it('should have a width, height and a placeholder', () => {
98 | const wrapper = createComponent({ ...item, width: 300, height: 300, placeholder: 'https://ik.imagekit.io/6xhf1gnexgdgk/igor2_HJhiHMa54.png' })
99 | const found = wrapper.find('.vue-image-kit > .vue-image-kit__placeholder > img')
100 | expect(found.exists()).toBe(true)
101 | const expected = {
102 | src: 'https://ik.imagekit.io/6xhf1gnexgdgk/igor2_HJhiHMa54.png',
103 | alt: 'Placeholder',
104 | style: 'width: 300px; height: 300px;'
105 | }
106 | expect(JSON.stringify(found.attributes())).toBe(JSON.stringify(expected))
107 | const main = wrapper.find('.vue-image-kit > .vue-image-kit__img')
108 | expect(main.exists()).toBe(true)
109 | expect(main.attributes().style).toBe('width: 300px; height: 300px;')
110 | })
111 |
112 | it('should have a alt attribute', () => {
113 | const wrapper = createComponent({ ...item, alt: 'Lion image' })
114 | const main = wrapper.find('.vue-image-kit > .vue-image-kit__img')
115 | expect(main.exists()).toBe(true)
116 | expect(main.attributes().alt).toBe('Lion image')
117 | })
118 |
119 | it('should have background color in the placeholder', async () => {
120 | const wrapper = createComponent({ ...item, width: 300, height: 300, placeholder: 'https://ik.imagekit.io/6xhf1gnexgdgk/igor2_HJhiHMa54.png', backgroundColor: '#f40' })
121 | const placeholder = wrapper.find('.vue-image-kit > .vue-image-kit__placeholder')
122 | expect(placeholder.exists()).toBe(true)
123 | expect(placeholder.attributes().style).toBe('background-color: rgb(255, 68, 0);') // #f40
124 | })
125 |
126 | it('should have different srcset from default', async () => {
127 | const wrapper = createComponent({ ...item, srcset: [400, 600, 900] })
128 | const main = wrapper.find('.vue-image-kit > .vue-image-kit__img')
129 | expect(main.exists()).toBe(true)
130 | wrapper.vm.triggerIntersection({ isIntersecting: true })
131 | await wrapper.vm.$nextTick()
132 | const expected = 'https://ik.imagekit.io/6xhf1gnexgdgk/tr:w-400/lion_BllLvaqVn.jpg 400w, https://ik.imagekit.io/6xhf1gnexgdgk/tr:w-600/lion_BllLvaqVn.jpg 600w, https://ik.imagekit.io/6xhf1gnexgdgk/tr:w-900/lion_BllLvaqVn.jpg 900w'
133 | expect(main.attributes().srcset).toBe(expected)
134 | })
135 |
136 | it('should have different sizes from default', async () => {
137 | const wrapper = createComponent({ ...item, srcset: [200, 250, 300] })
138 | const main = wrapper.find('.vue-image-kit > .vue-image-kit__img')
139 | expect(main.exists()).toBe(true)
140 | wrapper.vm.triggerIntersection({ isIntersecting: true })
141 | await wrapper.vm.$nextTick()
142 | const expected = '(max-width: 200px) 200px, (max-width: 250px) 250px, (max-width: 300px) 300px 1080px'
143 | expect(main.attributes().sizes).toBe(expected)
144 | })
145 |
146 | it('should have different sizes and srcset from default', async () => {
147 | const wrapper = createComponent({ ...item, srcset: [210, 220, 230], sizes: [210, 220, 230] })
148 | const main = wrapper.find('.vue-image-kit > .vue-image-kit__img')
149 | expect(main.exists()).toBe(true)
150 | wrapper.vm.triggerIntersection({ isIntersecting: true })
151 | await wrapper.vm.$nextTick()
152 | const expected = '(max-width: 210px) 210px, (max-width: 220px) 220px, (max-width: 230px) 230px 1080px'
153 | expect(main.attributes().sizes).toBe(expected)
154 | const expectedSrcset = 'https://ik.imagekit.io/6xhf1gnexgdgk/tr:w-210/lion_BllLvaqVn.jpg 210w, https://ik.imagekit.io/6xhf1gnexgdgk/tr:w-220/lion_BllLvaqVn.jpg 220w, https://ik.imagekit.io/6xhf1gnexgdgk/tr:w-230/lion_BllLvaqVn.jpg 230w'
155 | expect(main.attributes().srcset).toBe(expectedSrcset)
156 | })
157 |
158 | it('should have different default size', () => {
159 | const wrapper = createComponent({ ...item, defaultSize: 1366 })
160 | const main = wrapper.find('.vue-image-kit > .vue-image-kit__img')
161 | expect(main.exists()).toBe(true)
162 | expect(main.attributes().sizes).toContain('1366px')
163 | })
164 |
165 | it('should not have lazy load', async () => {
166 | const wrapper = createComponent({ ...item, lazyLoad: false })
167 | const main = wrapper.find('.vue-image-kit')
168 | expect(main.exists()).toBe(true)
169 | await timeout(1)
170 | expect(main.classes()).toContain('vue-image-kit--loaded')
171 | const placeholder = wrapper.find('.vue-image-kit__placeholder')
172 | expect(placeholder.exists()).toBe(false)
173 | expect(wrapper.vm.showCanvas).toBe(false)
174 | expect(wrapper.vm.$refs.normalLoad).toBeInstanceOf(HTMLImageElement)
175 | })
176 |
177 | it('should have a custom transform', async () => {
178 | // https://imagekit.io/features/advanced-image-manipulation-blur-rotate-crop-background-radius
179 | const customTransform = 'c-at_max,bl-1:r-20,bg-FFCFA1'
180 | const wrapper = createComponent({ ...item, customTransform })
181 | const main = wrapper.find('.vue-image-kit > .vue-image-kit__img')
182 | expect(main.exists()).toBe(true)
183 | wrapper.vm.triggerIntersection({ isIntersecting: true })
184 | await wrapper.vm.$nextTick()
185 | expect(main.attributes().srcset).toContain(customTransform)
186 | })
187 |
188 | it('should disconnect the observer on destroy', () => {
189 | const wrapper = createComponent({ ...item })
190 | wrapper.vm.timeOut = 300
191 | expect(wrapper.vm.observer).toStrictEqual(new IntersectionObserver({ observe: () => jest.fn(), unobserve: () => jest.fn() }))
192 | wrapper.destroy()
193 | expect(wrapper.vm.observer).toStrictEqual(new IntersectionObserver({ observe: () => jest.fn(), unobserve: () => jest.fn() }))
194 | const disconnect = jest.fn()
195 | expect(JSON.stringify(wrapper.vm.observer.disconnect)).toBe(JSON.stringify(disconnect))
196 | expect(wrapper.exists()).toBeFalsy()
197 | })
198 |
199 | it('should clear the timeout on disconnect', (done) => {
200 | const wrapper = createComponent({ ...item, width: 300, height: 300, placeholder: 'https://ik.imagekit.io/6xhf1gnexgdgk/igor2_HJhiHMa54.png' })
201 | expect(wrapper.exists()).toBe(true)
202 | wrapper.destroy()
203 | setTimeout(() => {
204 | expect(wrapper.vm.timeOut).toBe(null)
205 | done()
206 | }, 500)
207 | })
208 |
209 | it('should trigger intersection', async () => {
210 | const localVue = createLocalVue()
211 | const wrapper = mount(VueImageKit, {
212 | propsData: { ...item, width: 300, height: 300, placeholder: 'https://ik.imagekit.io/6xhf1gnexgdgk/igor2_HJhiHMa54.png' },
213 | localVue,
214 | stubs: {
215 | transition: false
216 | }
217 | })
218 | wrapper.vm.observer.disconnect = jest.fn()
219 | expect(wrapper.exists()).toBe(true)
220 | wrapper.vm.triggerIntersection({ isIntersecting: true })
221 | expect(wrapper.vm.timeOut).toBeNull()
222 | expect(wrapper.vm.showCanvas).toBeFalsy()
223 | const img = wrapper.find('.vue-image-kit__img')
224 | expect(img.exists()).toBe(true)
225 | const placeholder = wrapper.find('.vue-image-kit__placeholder')
226 | expect(placeholder.exists()).toBe(true)
227 | expect(wrapper.vm.$el.querySelector('.vue-image-kit__placeholder')).toBeDefined()
228 | wrapper.vm.$el.querySelector('.vue-image-kit__img').onload()
229 | await wrapper.vm.$nextTick()
230 | const placeholderAgain = wrapper.find('.vue-image-kit__placeholder')
231 | expect(placeholderAgain.exists()).toBe(false)
232 | expect(wrapper.vm.timeOut).not.toBeNull()
233 | await timeout(350)
234 | expect(wrapper.vm.$el.querySelector('.vue-image-kit__placeholder')).toBeNull()
235 | })
236 |
237 | it('should trigger intersection - method testing', () => {
238 | const localVue = createLocalVue()
239 | const wrapper = mount(VueImageKit, {
240 | propsData: { ...item, width: 300, height: 300, placeholder: 'https://ik.imagekit.io/6xhf1gnexgdgk/igor2_HJhiHMa54.png' },
241 | localVue,
242 | stubs: {
243 | transition: false
244 | }
245 | })
246 | const observe = jest.fn()
247 | const unobserve = jest.fn()
248 | const disconnect = jest.fn()
249 | const takeRecords = jest.fn()
250 | window.IntersectionObserver = jest.fn(() => ({
251 | observe,
252 | unobserve,
253 | disconnect,
254 | takeRecords
255 | }))
256 | expect(wrapper.exists()).toBe(true)
257 | wrapper.vm.initLazyLoad()
258 | wrapper.vm.triggerIntersection = jest.fn()
259 | wrapper.vm.setImgAttributes = jest.fn()
260 | expect(wrapper.vm.observer).not.toBeNull()
261 | expect(wrapper.vm.showCanvas).toBeTruthy()
262 | const img = wrapper.find('.vue-image-kit__img')
263 | expect(img.exists()).toBe(true)
264 | const placeholder = wrapper.find('.vue-image-kit__placeholder')
265 | expect(placeholder.exists()).toBe(true)
266 | expect(wrapper.vm.$el.querySelector('.vue-image-kit__placeholder')).toBeDefined()
267 | const observerCallback = window.IntersectionObserver.mock.calls[0][0]
268 | const mockEntry = { isIntersecting: true }
269 | observerCallback([mockEntry])
270 | expect(wrapper.vm.observer.observe).toHaveBeenCalled()
271 | expect(wrapper.vm.triggerIntersection).toHaveBeenCalledTimes(1)
272 | })
273 |
274 | it('should not trigger intersection', () => {
275 | const localVue = createLocalVue()
276 | const wrapper = mount(VueImageKit, {
277 | propsData: { ...item, width: 300, height: 300, placeholder: 'https://ik.imagekit.io/6xhf1gnexgdgk/igor2_HJhiHMa54.png' },
278 | localVue,
279 | stubs: {
280 | transition: false
281 | }
282 | })
283 | const observe = jest.fn()
284 | const unobserve = jest.fn()
285 | const disconnect = jest.fn()
286 | const takeRecords = jest.fn()
287 | window.IntersectionObserver = jest.fn(() => ({
288 | observe,
289 | unobserve,
290 | disconnect,
291 | takeRecords
292 | }))
293 | expect(wrapper.exists()).toBe(true)
294 | wrapper.vm.initLazyLoad()
295 | wrapper.vm.setImgAttributes = jest.fn()
296 | wrapper.vm.triggerIntersection = jest.fn()
297 | expect(wrapper.vm.observer).not.toBeNull()
298 | expect(wrapper.vm.showCanvas).toBeTruthy()
299 | const observerCallback = window.IntersectionObserver.mock.calls[0][0]
300 | const mockEntry = { isIntersecting: false }
301 | observerCallback([mockEntry])
302 | expect(wrapper.vm.setImgAttributes).not.toHaveBeenCalled()
303 | expect(wrapper.vm.observer.disconnect).not.toHaveBeenCalled()
304 | })
305 |
306 | it('should match snapshot', () => {
307 | const wrapper = createComponent({
308 | ...item,
309 | placeholder: '',
310 | backgroundColor: '',
311 | width: null,
312 | height: null,
313 | alt: '',
314 | srcset: [320, 480, 800],
315 | sizes: [],
316 | defaultSize: 1080,
317 | customTransform: ''
318 | })
319 |
320 | expect(wrapper.html()).toMatchSnapshot()
321 | })
322 | })
323 |
--------------------------------------------------------------------------------
/vue-image-kit.js:
--------------------------------------------------------------------------------
1 | // Import vue component
2 | import VueImageKit from './src/components/VueImageKit.vue'
3 |
4 | // install function executed by Vue.use()
5 | const install = function (Vue) {
6 | if (install.installed) return
7 | install.installed = true
8 | Vue.component('VueImageKit', VueImageKit)
9 | }
10 |
11 | // Create module definition for Vue.use()
12 | const plugin = {
13 | install
14 | }
15 |
16 | // To auto-install when vue is found
17 | // eslint-disable-next-line no-redeclare
18 | /* global window, global */
19 | let GlobalVue = null
20 |
21 | if (typeof window !== 'undefined') {
22 | GlobalVue = window.Vue
23 | } else if (typeof global !== 'undefined') {
24 | GlobalVue = global.Vue
25 | }
26 |
27 | if (GlobalVue) {
28 | GlobalVue.use(plugin)
29 | }
30 |
31 | // Inject install function into component - allows component
32 | // to be registered via Vue.use() as well as Vue.component()
33 | VueImageKit.install = install
34 |
35 | // Export component by default
36 | export default VueImageKit
37 |
38 | // Export single (backwards compatibility)
39 | export { VueImageKit }
40 |
--------------------------------------------------------------------------------