├── .github
└── workflows
│ └── workflow.yml
├── .gitignore
├── .gitmodules
├── .npmignore
├── .prettierignore
├── .prettierrc
├── .scripts
├── get_gh_pages_url.js
└── publish_storybook.sh
├── .storybook
├── main.js
├── manager.js
└── preview.js
├── CHANGELOG.md
├── LICENSE
├── README.md
├── codecov.yml
├── cypress.config.cjs
├── cypress
├── fixtures
│ └── example.json
└── support
│ ├── commands.js
│ ├── component-index.html
│ ├── component.js
│ └── e2e.js
├── jest.config.js
├── nyc.config.cjs
├── package.json
├── pnpm-lock.yaml
├── slim.config.js
├── speedo.gif
├── src
├── __tests__
│ ├── custom-segment-labels.js
│ ├── custom-text.js
│ ├── force-render.js
│ ├── format-value.js
│ ├── index.js
│ └── update-values.js
├── core
├── index.d.ts
├── index.vue
├── props.js
└── stories
│ └── Speedometer.stories.js
├── tests
├── E2E.md
├── components
│ ├── ValueUpdate.spec.js
│ ├── ValueUpdate.vue
│ ├── VueSpeedometer.spec.js
│ └── force-render
│ │ ├── ForceRender.spec.js
│ │ └── ForceRender.vue
├── plugins
│ ├── cypress-webpack-config.js
│ └── index.js
└── support
│ ├── commands.js
│ └── index.js
├── vite.config.js
└── vitest.config.js
/.github/workflows/workflow.yml:
--------------------------------------------------------------------------------
1 | name: Test Coverage
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: ubuntu-latest
13 |
14 | strategy:
15 | matrix:
16 | node-version: [18.x]
17 |
18 | steps:
19 | - name: Checkout repository
20 | uses: actions/checkout@v2
21 | with:
22 | submodules: recursive
23 |
24 | # - name: Init Submodule to base repo
25 | # run: git submodule update --init
26 |
27 | - name: Set up Node.js ${{ matrix.node-version }}
28 | uses: actions/setup-node@v1
29 | with:
30 | node-version: ${{ matrix.node-version }}
31 |
32 | - name: Install dependencies
33 | run: yarn
34 |
35 | # ref: https://github.com/cypress-io/github-action
36 | - name: Setup cypress
37 | uses: cypress-io/github-action@v4
38 | with:
39 | browser: chrome
40 | component: true
41 | # just perform install
42 | runTests: false
43 |
44 | - name: Run the tests and generate coverage report
45 | run: yarn full-test && yarn report:combined
46 |
47 | - name: Upload coverage to Codecov
48 | uses: codecov/codecov-action@v2
49 |
50 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | *.log
3 | .idea
4 | dist
5 | *.sublime-*
6 | .DS_Store
7 | *.code-workspace
8 |
9 | coverage
10 | *-coverage
11 | cypress/videos
12 |
13 | .nyc_output
14 | reports
15 | instrumented
16 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "lib/d3-speedometer"]
2 | path = lib/d3-speedometer
3 | url = https://github.com/palerdot/react-d3-speedometer
4 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | lib
2 | src
3 | stories
4 | .storybook
5 |
6 | .scripts
7 |
8 | .git
9 | .gitmodules
10 |
11 | .babelrc
12 | *.gif
13 | *.code-workspace
14 | .DS_Store
15 |
16 | *.config.js
17 |
18 | .prettierignore
19 | .prettierrc
20 |
21 | .travis.yml
22 |
23 | yarn.lock
24 | yarn-error.log
25 |
26 | *-coverage
27 | coverage
28 | cypress*
29 |
30 | tests
31 | codecov.yml
32 | .github
33 | reports
34 |
35 |
36 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | /package.json
2 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": false,
3 | "tabWidth": 2,
4 | "arrowParens": "always",
5 | "printWidth": 80,
6 | "trailingComma": "es5"
7 | }
8 |
--------------------------------------------------------------------------------
/.scripts/get_gh_pages_url.js:
--------------------------------------------------------------------------------
1 | // IMPORTANT
2 | // ---------
3 | // This is an auto generated file with React CDK.
4 | // Do not modify this file.
5 |
6 | // const parse = require('git-url-parse');
7 | import parse from "git-url-parse"
8 |
9 | var ghUrl = process.argv[2]
10 | const parsedUrl = parse(ghUrl)
11 |
12 | const ghPagesUrl = "https://" + parsedUrl.owner + ".github.io/" + parsedUrl.name
13 | console.log(ghPagesUrl)
14 |
--------------------------------------------------------------------------------
/.scripts/publish_storybook.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # IMPORTANT
4 | # ---------
5 | # This is an auto generated file with React CDK.
6 | # Do not modify this file.
7 |
8 | set -e # exit with nonzero exit code if anything fails
9 |
10 | # get GIT url
11 |
12 | GIT_URL=`git config --get remote.origin.url`
13 | if [[ $GIT_URL == "" ]]; then
14 | echo "This project is not configured with a remote git repo".
15 | exit 1
16 | fi
17 |
18 | # clear and re-create the out directory
19 | rm -rf .out || exit 0;
20 | mkdir .out;
21 |
22 | # run our compile script, discussed above
23 | # build-storybook -o .out
24 | npm run build-storybook
25 |
26 | # go to the out directory and create a *new* Git repo
27 | cd .out
28 | git init
29 |
30 | # inside this git repo we'll pretend to be a new user
31 | git config user.name "palerdot GH Pages Bot"
32 | git config user.email "ghpages-bot@palerdot.in"
33 |
34 | # The first and only commit to this new Git repo contains all the
35 | # files present with the commit message "Deploy to GitHub Pages".
36 | git add .
37 | git commit -m "Deploy Storybook to GitHub Pages"
38 |
39 | # Force push from the current repo's master branch to the remote
40 | # repo's gh-pages branch. (All previous history on the gh-pages branch
41 | # will be lost, since we are overwriting it.) We redirect any output to
42 | # /dev/null to hide any sensitive credential data that might otherwise be exposed.
43 | git push --force --quiet $GIT_URL master:gh-pages > /dev/null 2>&1
44 | cd ..
45 | rm -rf .out
46 |
47 | echo ""
48 | echo "=> Storybook deployed to: `node .scripts/get_gh_pages_url.js $GIT_URL`"
49 |
--------------------------------------------------------------------------------
/.storybook/main.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | stories: [
3 | "../src/stories/**/*.stories.mdx",
4 | "../src/stories/**/*.stories.@(js|jsx|ts|tsx)",
5 | ],
6 |
7 | addons: ["@storybook/addon-links", "@storybook/addon-essentials"],
8 |
9 | async viteFinal(config, { configType }) {
10 | // customize the Vite config here
11 | return {
12 | ...config,
13 | define: {
14 | ...config.define,
15 | global: "window",
16 | },
17 | }
18 | },
19 |
20 | framework: {
21 | name: "@storybook/vue3-vite",
22 | options: {}
23 | },
24 |
25 | docs: {
26 | autodocs: true
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/.storybook/manager.js:
--------------------------------------------------------------------------------
1 | import { addons } from "@storybook/addons"
2 | import { themes, create } from "@storybook/theming/create"
3 | import theme from "../src/core/theme"
4 |
5 | const speedoTheme = create({
6 | ...themes.dark,
7 | ...theme,
8 |
9 | brandTitle: "vue-speedometer",
10 | brandUrl: "https://github.com/palerdot/vue-speedometer",
11 | })
12 |
13 | addons.setConfig({
14 | showPanel: true,
15 | theme: speedoTheme,
16 | })
17 |
--------------------------------------------------------------------------------
/.storybook/preview.js:
--------------------------------------------------------------------------------
1 | // ref: https://www.npmjs.com/package/@storybook/vue3
2 | // ref: https://github.com/storybookjs/storybook/issues/19295
3 | import { setup } from "@storybook/vue3"
4 |
5 | // DEVELOPMENT: 'src/index.js'
6 | import VueSpeedometer from "../src/index.vue"
7 | // PRODUCTION build testing
8 | // import VueSpeedometer from "../dist/index"
9 | //
10 | setup(app => {
11 | app.component("vue-speedometer", VueSpeedometer)
12 | app.mixin({
13 | /* My mixin */
14 | })
15 | })
16 |
17 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | ## `v3.0.1`
4 |
5 | - `v3` General Availability - Happy Vue Coding!
6 |
7 | ## v3.0.0-rc0
8 |
9 | - `Vue 3 Support - SFC/composition api`: Rewrite with SFC and composition api
10 | - `segmentLabelFormatter` prop
11 | - Experimental slim build
12 | - Core update `v2.2.1`
13 |
14 | -------------------------
15 |
16 | ## v2.0
17 |
18 | - Compatible with `vue v3.0` (still uses options api under the hood)
19 |
20 | -------------------------
21 |
22 | ## 1.8.0
23 | - **Bugfix**: Always take new value for `value` prop. This fixes the bug when value will not go to `0`.
24 | Resolves https://github.com/palerdot/vue-speedometer/issues/22
25 |
26 | ## 1.7.0
27 | - [`CORE`][0.14.0]
28 | - `valueTextFontWeight` config/prop. ref: https://codesandbox.io/s/competent-grothendieck-73cz8?file=/src/App.vue
29 | - `cypress` tests for `valueTextFontWeight`
30 |
31 | ## 1.6.0
32 | - [`CORE`][0.13.1]
33 | - `CustomSegmentLabelPosition`, `Transition` types for both Typescript and JS. ref - https://codesandbox.io/s/modest-cookies-tnhqx?file=/src/App.vue
34 | - `100%` Test coverage
35 | - `codecov`, `github actions` integration
36 | - `cypress` e2e tests
37 |
38 | ## 1.5.0
39 | - removed `@babel/runtime-corejs2` as dependency
40 |
41 | ## 1.4.0
42 | - [`CORE`][0.11.0]
43 | - migrated to `lodash-es` from `lodash` for better tree shaking. Exporting `themes` from core for better reusablility.
44 | - removed `lodash` dependency. ref: https://codesandbox.io/s/fragrant-haze-f3cqz
45 |
46 | ## 1.3.1
47 | - link banner gif from github master tree
48 |
49 | ## 1.3.0
50 | - [`CORE`][0.10.0]
51 |
52 | ## 1.2.1
53 | - *Fix Typescript declaration*. Use proper Vue type declaration. Fixes typescript support problem for Vue/Typescript.
54 |
55 | ## 1.2.0
56 | - `Typescript` support. [CORE][0.9.0]
57 |
58 | ## 1.1.0
59 | - [bugfix] Fix `forceRender` prop not updating from `true => false`. Fixes - https://github.com/palerdot/vue-speedometer/issues/4
60 |
61 | ## 1.0.4
62 | - [CORE][0.8.0]
63 |
64 | ## 1.0.3
65 | - core sync `[v0.7.2, v0.7.3]`
66 |
67 | ## 1.0.2
68 | - core sync `[v0.7.1]`
69 |
70 | ## 1.0.1 beta
71 | - minor `README` fixes
72 | ## 1.0.0.beta
73 | - Initial `beta` release - https://codesandbox.io/s/vue-template-5yuw8
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Arun Kumar
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 | # vue-speedometer
2 |
3 | **vue-speedometer** is a Vue component library for showing speedometer like gauge using d3.
4 |
5 | [](https://codecov.io/gh/palerdot/vue-speedometer)
6 | [](https://github.com/prettier/prettier)
7 | [](https://github.com/palerdot/vue-speedometer/actions/workflows/workflow.yml/badge.svg)
8 |
9 | [](https://img.shields.io/npm/v/vue-speedometer/latest?style=flat-square)
10 |
11 | 
12 |
13 | **IMPORTANT** `v3.0` is released which is a complete rewrite with composition api/SFC (single file components). Minimum required vue version is `v3.3`. If you are using Vue 3 composition api, please upgrade to `v3`
14 |
15 | Note: v2.x is still compatible with `Vue 3`. Please use latest v1.x (v1.8.0 at the time of writing) if you are using `Vue 2`.
16 |
17 | ## Usage:
18 |
19 | **pnpm:**
20 | `pnpm add vue-speedometer`
21 |
22 | **Yarn:**
23 | `yarn add vue-speedometer`
24 |
25 | **NPM:**
26 | `npm install --save vue-speedometer`
27 |
28 | ```javascript
29 | // import the component
30 |
33 | // and use it in your component like
34 |
35 |
36 |
37 | ```
38 |
39 | ### Slim Build (Experimental):
40 |
41 | There is a `Slim` build available without bundling `d3`. This project uses `d3` *micro bundles*. If your project also uses `d3` *microbundles*, you can opt for **slim build**. Necessary `d3` dependencies required for slim build to work are - `d3-array`, `d3-color`, `d3-ease`, `d3-format`, `d3-interpolate`, `d3-scale`, `d3-selection`, `d3-shape`, `d3-transition`.
42 | ```javascript
43 | // sample slim build usage
44 | import VueSpeedometer from "vue-speedometer/slim"
45 | // and use it
46 |
47 | ```
48 |
49 |
50 | ## Examples:
51 |
52 | You can view [Live Examples here](https://palerdot.in/vue-speedometer/)
53 |
54 |
55 | ## Configuration Options:
56 |
57 | | prop | type | default | comments |
58 | | ------------|:--------------:| --------:| ---------|
59 | | value | Number | 0 | Make sure your value is between your `minValue` and `maxValue` |
60 | | minValue | Number | 0 | |
61 | | maxValue | Number | 1000 | |
62 | | segments | Number | 5 | Number of segments in the speedometer. Please note, `segments` is calculated with [d3-ticks]() which is an approximate count that is uniformly spaced between min and max. Please refer to [d3-ticks](https://github.com/d3/d3-scale/blob/master/README.md#continuous_ticks) and [d3-array ticks](https://github.com/d3/d3-array#ticks) for more detailed info. |
63 | | maxSegmentLabels | Number | value from 'segments' prop | Limit the number of segment labels to displayed. This is useful for acheiving a gradient effect by giving arbitrary large number of `segments` and limiting the labels with this prop. [See Live Example](https://palerdot.in/vue-speedometer/?path=/story/vue-speedometer--gradient-effect-with-large-number-of-segments-and-maxsegmentlabels-config). Please note, `maxSegmentLabels` is calculated with [d3-ticks]() which is an approximate count that is uniformly spaced between min and max. Please refer to [d3-ticks](https://github.com/d3/d3-scale/blob/master/README.md#continuous_ticks) and [d3-array ticks](https://github.com/d3/d3-array#ticks) for more detailed info. |
64 | | forceRender | Boolean | false | After initial rendering/mounting, when props change, only the `value` is changed and animated to maintain smooth visualization. But, if you want to force rerender the whole component like change in segments, colors, dimensions etc, you can use this option to force rerender of the whole component on props change. |
65 | | width | Number | 300 | **diameter** of the speedometer and the **width** of the svg element |
66 | | height | Number | 300 | height of the svg element. Height of the speedometer is always half the width since it is a **semi-circle**. For fluid width, please refere to `fluidWidth` config |
67 | | dimensionUnit | String | px | Default to `px` for `width/height`. Possible values - `"em" , "ex" , "px" , "in" , "cm" , "mm" , "pt" , ,"pc"` ... Please refer to [specification](https://developer.mozilla.org/en-US/docs/Web/SVG/Content_type#Length) for more details |
68 | | fluidWidth | Boolean | false | If `true` takes the width of the parent component. See [Live Example](https://palerdot.in/vue-speedometer/?selectedStory=Fluid%20Width%20view&full=0&down=0&left=1&panelRight=0&downPanel=kadirahq%2Fstorybook-addon-actions%2Factions-panel) for more details |
69 | | needleColor | String | steelblue | Should be a valid color code - colorname, hexadecimal name or rgb value. Should be a valid input for [d3.interpolateHsl](https://github.com/d3/d3-interpolate#interpolateHsl) |
70 | | startColor | String | #FF471A | Should be a valid color code - colorname, hexadecimal name or rgb value. Should be a valid input for [d3.interpolateHsl](https://github.com/d3/d3-interpolate#interpolateHsl) |
71 | | endColor | String | #33CC33 | Should be a valid color code - colorname, hexadecimal name or rgb value. Should be a valid input for [d3.interpolateHsl](https://github.com/d3/d3-interpolate#interpolateHsl) |
72 | | segmentColors | Array (of colors) | [] | Custom segment colors can be given with this option. Should be an array of valid color codes. If this option is given **startColor** and **endColor** options will be ignored. |
73 | | needleTransition | String (JS) / Transition (TS) | easeQuadInOut | [d3-easing-identifiers](https://github.com/d3/d3-ease) - easeLinear, easeQuadIn, easeQuadOut, easeQuadInOut, easeCubicIn, easeCubicOut, easeCubicInOut, easePolyIn, easePolyOut, easePolyInOut, easeSinIn, easeSinOut, easeSinInOut, easeExpIn, easeExpOut, easeExpInOut, easeCircleIn, easeCircleOut, easeCircleInOut, easeBounceIn, easeBounceOut, easeBounceInOut, easeBackIn, easeBackOut, easeBackInOut, easeElasticIn, easeElasticOut, easeElasticInOut, easeElastic. There is a helper Object/Type 'Transtion', which you can import like `import { Transition } from 'vue-speedometer'` and use it like `Transition.easeElastic`. This works for both JS and Typescript. For `type(script)` definitions, please refer [here](./src/index.d.ts). |
74 | | needleTransitionDuration | number | 500 | Time in milliseconds. |
75 | | needleHeightRatio | Float (between 0 and 1) | 0.9 | Control the height of the needle by giving a number/float between `0` and `1`. Default height ratio is `0.9`. |
76 | | ringWidth | Number | 60 | Width of the speedometer ring. |
77 | | textColor | String | #666 | Should be a valid color code - colorname, hexadecimal name or rgb value. Used for both showing the current value and the segment values |
78 | | valueFormat | String | | should be a valid format for [d3-format](https://github.com/d3/d3-format#locale_format). By default, no formatter is used. You can use a valid d3 format identifier (for eg: `d` to convert float to integers), to format the values. **Note:** This formatter affects all the values (current value, segment values) displayed in the speedometer |
79 | | segmentValueFormatter | Function | value => value | Custom segment values formatter function. This function is applied after 'valueFormat' prop if present. |
80 | | currentValueText | String | ${value} | Should be provided a string which should have **${value}** placeholder which will be replaced with current value. By default, current value is shown (formatted with `valueFormat`). For example, if current Value is 333 if you would like to show `Current Value: 333`, you should provide a string **`Current Value: ${value}`**. See [Live Example](https://palerdot.in/vue-speedometer/?selectedKind=vue-speedometer&selectedStory=Custom%20Current%20Value%20Text&full=0&down=1&left=1&panelRight=0) |
81 | | currentValuePlaceholderStyle | String | ${value} | Should be provided a placeholder string which will be replaced with current value in `currentValueTextProp`. For example: you can use ruby like interpolation by giving following props - ``. This is also helpful if you face `no-template-curly-in-string` eslint warnings and would like to use different placeholder for current value |
82 | | customSegmentStops | Array | [] | Array of values **starting** at `min` value, and **ending** at `max` value. This configuration is useful if you would like to split the segments at custom points or have unequal segments at preferred values. If the values does not begin and end with `min` and `max` value respectively, an error will be thrown. This configuration will override `segments` prop, since total number of segments will be `length - 1` of `customSegmentProps`. For example, `[0, 50, 75, 100]` value will have three segments - `0-50`, `50-75`, `75-100`. See [Live Example](https://palerdot.in/vue-speedometer/?path=/story/vue-speedometer--custom-segment-stops) |
83 | | customSegmentLabels | Array`` | [] | Takes an array of `CustomSegmentLabel` objects. Each object has following keys for custom rendering of labels - `text`, `fontSize`, `color`, `position: OUTSIDE/INSIDE`. For `position`, there is a helper `CustomSegmentLabelPosition` Object/Type which you can import like `import { CustomSegmentLabelPosition } from 'vue-speedometer'`, and use it like `CustomSegmentLabelPosition.Inside / CustomSegmentLabelPosition.Outside`. This works for both JS and Typescript. For `type(script)` definitions, please refer [here](./src/index.d.ts). |
84 | | labelFontSize | String | 14px | Font size for segment labels/legends |
85 | | valueTextFontSize | String | 16px | Font size for current value text |
86 | | valueTextFontWeight | String | bold | Font weight for current value text. Any valid font weight identifier (500, bold etc) can be used. |
87 | | paddingHorizontal | Number | 0 | Provides right/left space for the label text. Takes a number (without explicit unit, unit will be taken from dimensionUnit config which defaults to px). Helpful when using a bigger font size for label texts. |
88 | | paddingVertical | Number | 0 | Provides top/bottom space for the current value label text below the needle. Takes a number (without explicit unit, unit will be taken from dimensionUnit config which defaults to px). Helpful when using a bigger font size for label texts. |
89 | | svgAriaLabel | String | Vue Speedometer | SVG aria-label property for Accessibility purposes |
90 |
91 | ## Examples
92 |
93 | You can view [Live Examples here](https://palerdot.in/vue-speedometer/?path=/story/vue-speedometer--default-with-no-config)
94 |
95 | #### Default with no config - [Live Example](https://palerdot.in/vue-speedometer/?path=/story/vue-speedometer--default-with-no-config)
96 |
97 | ```javascript
98 | export default {
99 | components: { VueSpeedometer },
100 | template: ``,
101 | }
102 | ```
103 |
104 | #### With configurations - [Live Example](https://palerdot.in/vue-speedometer/?path=/story/vue-speedometer--configuring-values)
105 |
106 | ```javascript
107 | export default {
108 | components: { VueSpeedometer },
109 | template: ``,
110 | }
111 | ```
112 |
113 | #### Custom Segment Labels - [Live Example](https://palerdot.in/vue-speedometer/?path=/story/vue-speedometer--custom-segment-labels)
114 |
115 | ```javascript
116 | // 'customSegmentLabels' prop takes an array of 'CustomSegmentLabel' Object
117 | /*
118 | type CustomSegmentLabel = {
119 | text?: string
120 | position?: OUTSIDE/INSIDE
121 | fontSize?: string
122 | color?: string
123 | }
124 | */
125 |
126 | export default {
127 | components: { VueSpeedometer },
128 | template: `
129 |
268 | `,
269 | }
270 | ```
271 |
272 | #### Force Render component on props change - [Live Example](https://palerdot.in/vue-speedometer/?path=/story/vue-speedometer--force-render-the-component)
273 |
274 | ```javascript
275 | // By default, when props change, only the value prop is updated and animated.
276 | // This is to maintain smooth visualization and to ignore breaking appearance changes like segments, colors etc.
277 | // You can override this behaviour by giving forceRender: true
278 |
279 | export default {
280 | components: { VueSpeedometer },
281 | template: `
282 |
236 | By default, on props change only the current value and needle transition is updated.
237 | Force render completly re-renders the whole component on update.
238 | This is helpful for features like dynmaic width/height on resize
239 |
449 | `,
450 | })
451 |
--------------------------------------------------------------------------------
/tests/E2E.md:
--------------------------------------------------------------------------------
1 | ### E2E (cypress)
2 |
3 | This is a document with pointers how the `e2e cypress` tests is setup with code coverage with istanbul/nyc.
4 |
5 | Uses `cypress-vue-unit-test`, a.k.a `@cypress/vue` - https://www.npmjs.com/package/cypress-vue-unit-test
6 | Uses `@cypress/code-coverage/`.
7 |
8 | All `e2e` tests are within `tests` folder.
9 |
10 | There were some confusions ons correct package name to include in the config
11 | files. This setup was done right at the time when the package is in the process of moving to `@cypress/vue` (it is not completely
12 | moved at the time of this writing). As a result of this confusion, the setup was sourcing empty files from `@cypress/vue` (which did
13 | not have files published in this namespace)
14 |
15 |
16 | It is important to note that `@cypress/vue` works only with webpack setup. So we had to configure/provision `@cypress/webpack-preprocessor`,
17 | just to make the `e2e` tests run for vue files. More on this setup in next section.
18 |
19 | Following files are configured to use `cypress-vue-unit-test`, a.k.a `@cypress/vue`.
20 |
21 | - Support file: `tests/support/index.js`
22 | - Config file: `tests/plugins/index.js` (main config file)
23 |
24 | #### Configuration
25 |
26 | All the configuration related logic is inside `tests/plugins/index.js`.
27 | Custom webpack configuration which uses `babel-plugin-istanbul` for code coverage is inside `/tests/plugins/cypress-webpack-config.js`
28 |
29 | Roughly this setup works in the following way
30 | ```javascript
31 | @cypress/vue
32 | |
33 | |
34 | |
35 | reads `tests/support/index.js` and `tests/plugins/index.js`
36 | |
37 | |
38 | |
39 | reads webpack config from `tests/plugins/cypress-webpack-config` with `babel-plugin-istanbul`
40 | added in `plugins: [istanbul]`
41 | |
42 | |
43 | |
44 | Generates code coverage and runs tests
45 | ```
46 |
47 | ### Configuring `nyc`
48 |
49 | `nyc` is the command line tool for `istanbul`. It is currently configured in `nyc.config.js`.
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/tests/components/ValueUpdate.spec.js:
--------------------------------------------------------------------------------
1 | import { mount } from "cypress/vue"
2 | // import VueSpeedometer from "../../src/index.vue"
3 | // NOTE: we are manually instrumenting using NYC and using it for running cypress tests
4 | // import VueSpeedometer from "../../instrumented/index.vue"
5 | import ValueUpdate from "./ValueUpdate.vue"
6 |
7 | describe("VueSpeedometer", () => {
8 | it("Updates component correctly", () => {
9 | mount(ValueUpdate, {
10 | props: {},
11 | })
12 | // now we can use any Cypress command to interact with the component
13 | // https://on.cypress.io/api
14 | cy.get(".current-value").contains("333")
15 |
16 | // click the button
17 | cy.get("button#reset-value").click()
18 |
19 | // now we should have the updated value
20 | cy.get(".current-value").contains("0")
21 |
22 | // we will update the value again
23 | cy.get("button#update-value").click()
24 |
25 | // we should get the updated value again
26 | cy.get(".current-value").contains("333")
27 | })
28 | })
29 |
--------------------------------------------------------------------------------
/tests/components/ValueUpdate.vue:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
{{ greeting }}
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/tests/components/VueSpeedometer.spec.js:
--------------------------------------------------------------------------------
1 | import { mount } from "cypress/vue"
2 | // import VueSpeedometer from "../../src/index.vue"
3 | import VueSpeedometer from "../../instrumented/index.vue"
4 |
5 | describe("VueSpeedometer", () => {
6 | it("renders the component with correct value", () => {
7 | mount(VueSpeedometer, {
8 | propsData: {
9 | value: 333,
10 | },
11 | })
12 |
13 | // now we can use any Cypress command to interact with the component
14 | // https://on.cypress.io/api
15 | cy.contains("333")
16 | })
17 |
18 | it("displays default font weight correctly", () => {
19 | mount(VueSpeedometer, {
20 | propsData: {
21 | value: 333,
22 | },
23 | })
24 |
25 | // now we can use any Cypress command to interact with the component
26 | // https://on.cypress.io/api
27 | cy.get("svg.speedometer")
28 | .find(".current-value")
29 | // bold => 700
30 | .should("have.css", "font-weight", "700")
31 | })
32 |
33 | it("displays 'valueTextFontWeight' correctly", () => {
34 | mount(VueSpeedometer, {
35 | propsData: {
36 | valueTextFontWeight: "500",
37 | },
38 | })
39 |
40 | // now we can use any Cypress command to interact with the component
41 | // https://on.cypress.io/api
42 | cy.get("svg.speedometer")
43 | .find(".current-value")
44 | // bold => 700
45 | .should("have.css", "font-weight", "500")
46 | })
47 | })
48 |
--------------------------------------------------------------------------------
/tests/components/force-render/ForceRender.spec.js:
--------------------------------------------------------------------------------
1 | import { mount } from "cypress/vue"
2 | // import VueSpeedometer from "../../../src/index.vue"
3 | // import VueSpeedometer from "../../../instrumented/index.vue"
4 | import ForceRender from "./ForceRender.vue"
5 |
6 | describe("VueSpeedometer", () => {
7 | it("updates component normally", () => {
8 | mount(ForceRender, {
9 | props: {},
10 | })
11 |
12 | // now we can use any Cypress command to interact with the component
13 | // https://on.cypress.io/api
14 | cy.contains("333")
15 | cy.get(".speedo-segment").should("have.length", 5)
16 |
17 | // click the button
18 | cy.get("button#normal-update").click()
19 |
20 | // now we should have the updated value
21 | cy.contains("777")
22 |
23 | // we did not force rendered; our segments should be the same(5)
24 | cy.get(".speedo-segment").should("have.length", 5)
25 | })
26 |
27 | it("force renders the component with correct value", () => {
28 | mount(ForceRender, {
29 | props: {},
30 | })
31 |
32 | // now we can use any Cypress command to interact with the component
33 | // https://on.cypress.io/api
34 | cy.contains("333")
35 | cy.get(".speedo-segment").should("have.length", 5)
36 |
37 | // click the button
38 | cy.get("button#force-render").click()
39 |
40 | // now we should have the updated value
41 | cy.contains("417")
42 |
43 | // we force rendered; our segments should be 10 (from 5)
44 | cy.get(".speedo-segment").should("have.length", 10)
45 | })
46 | })
47 |
--------------------------------------------------------------------------------
/tests/components/force-render/ForceRender.vue:
--------------------------------------------------------------------------------
1 |
20 |
21 |
22 |
{{ greeting }}
23 |
26 |
29 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/tests/plugins/cypress-webpack-config.js:
--------------------------------------------------------------------------------
1 | // ref: https://vue-loader.vuejs.org/guide/#manual-setup
2 | const VueLoaderPlugin = require("vue-loader/lib/plugin")
3 |
4 | module.exports = {
5 | module: {
6 | rules: [
7 | {
8 | test: /\.m?js$/,
9 | exclude: /(node_modules|bower_components)/,
10 | use: {
11 | loader: "babel-loader",
12 | options: {
13 | presets: ["@babel/preset-env"],
14 | plugins: ["istanbul"],
15 | },
16 | },
17 | },
18 | {
19 | test: /\.vue$/,
20 | loader: "vue-loader",
21 | },
22 | ],
23 | },
24 | plugins: [
25 | // make sure to include the plugin!
26 | new VueLoaderPlugin(),
27 | ],
28 | }
29 |
--------------------------------------------------------------------------------
/tests/plugins/index.js:
--------------------------------------------------------------------------------
1 | // const preprocessor = require('@cypress/vue/dist/plugins/webpack');
2 | const webpackPreprocessor = require("@cypress/webpack-preprocessor")
3 | const coverage = require("@cypress/code-coverage/task")
4 |
5 | module.exports = (on, config) => {
6 | coverage(on, config)
7 |
8 | // webpackPreprocessor(on, config)
9 | const webpackOptions = {
10 | // send in the options from your webpack.config.js, so it works the same
11 | // as your app's code
12 | webpackOptions: require("./cypress-webpack-config"),
13 | watchOptions: {},
14 | }
15 | on("file:preprocessor", webpackPreprocessor(webpackOptions))
16 |
17 | // IMPORTANT return the config object
18 | return config
19 | }
20 |
--------------------------------------------------------------------------------
/tests/support/commands.js:
--------------------------------------------------------------------------------
1 | // ***********************************************
2 | // This example commands.js shows you how to
3 | // create various custom commands and overwrite
4 | // existing commands.
5 | //
6 | // For more comprehensive examples of custom
7 | // commands please read more here:
8 | // https://on.cypress.io/custom-commands
9 | // ***********************************************
10 | //
11 | //
12 | // -- This is a parent command --
13 | // Cypress.Commands.add("login", (email, password) => { ... })
14 | //
15 | //
16 | // -- This is a child command --
17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
18 | //
19 | //
20 | // -- This is a dual command --
21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
22 | //
23 | //
24 | // -- This is will overwrite an existing command --
25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
26 |
--------------------------------------------------------------------------------
/tests/support/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Component Testing resets
17 | // NOTE: enable this after migration to '@cypress/vue' is complete
18 | // require('@cypress/vue/dist/support')
19 | require("cypress-vue-unit-test/dist/support")
20 | require("@cypress/code-coverage/support")
21 |
22 | // Import commands.js using ES2015 syntax:
23 | // import './commands'
24 |
25 | // Alternatively you can use CommonJS syntax:
26 | require("./commands")
27 |
28 | // Import any global stylesheets here
29 | // require('../../src/styles/index.scss') // 💅
30 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import path from "path"
2 | import { defineConfig } from "vite"
3 | import vue from "@vitejs/plugin-vue"
4 | import terser from "@rollup/plugin-terser"
5 | import { nodeResolve } from "@rollup/plugin-node-resolve"
6 | // import analyze from "rollup-plugin-analyzer"
7 |
8 | const devMode = process.env.NODE_ENV === "development"
9 |
10 | // ref: https://blog.openreplay.com/the-ultimate-guide-to-getting-started-with-the-rollup-js-javascript-bundler
11 | function terserConfig() {
12 | return terser({
13 | ecma: 2020,
14 |
15 | mangle: { toplevel: true },
16 |
17 | compress: {
18 | module: true,
19 | toplevel: true,
20 | unsafe_arrows: true,
21 | drop_console: !devMode,
22 | drop_debugger: !devMode,
23 | },
24 |
25 | output: { quote_style: 1 },
26 | })
27 | }
28 |
29 | // https://vitejs.dev/config/
30 | export default defineConfig({
31 | build: {
32 | lib: {
33 | name: "VueSpeedometer",
34 | entry: path.resolve(__dirname, "src/index.vue"),
35 | fileName: (format) => `vue-speedometer.${format}.js`,
36 | },
37 | rollupOptions: {
38 | external: ["vue"],
39 | output: {
40 | globals: {
41 | vue: "Vue",
42 | },
43 | sourcemap: devMode ? "inline" : false,
44 | plugins: [terserConfig()],
45 | },
46 | // IMPORTANT: This plugins is different from output plugins
47 | plugins: [
48 | nodeResolve(),
49 | // analyze({
50 | // summaryOnly: true,
51 | // filterSummary: true,
52 | // }),
53 | ],
54 | },
55 | },
56 | resolve: {
57 | alias: {
58 | // ref: https://github.com/vitejs/vite/discussions/4158
59 | // needed to enable template compiling for cypress
60 | // vue: "vue/dist/vue.esm-bundler.js",
61 | },
62 | },
63 | plugins: [vue()],
64 | })
65 |
--------------------------------------------------------------------------------
/vitest.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite"
2 | import Vue from "@vitejs/plugin-vue"
3 |
4 | export default defineConfig({
5 | plugins: [Vue()],
6 | test: {
7 | globals: true,
8 | environment: "happy-dom",
9 | // spec.js belongs to cypress
10 | include: ["src/**/*.test.js"],
11 | },
12 | })
13 |
--------------------------------------------------------------------------------