diff --git a/.eslintrc.js b/.eslintrc.js index e0bf101..d31f6c5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -14,6 +14,7 @@ module.exports = { "max-len": [2, 120, 8], "no-restricted-syntax": "off", "guard-for-in": "off", + "no-case-declarations": "off", }, parserOptions: { parser: "babel-eslint", diff --git a/README.md b/README.md index 4950c0c..df567fb 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ This table below provides a quick overview over all available options. To gain m | **[`size`](#size)** | Number | >=0 | 200 | | | **[`line`](#line)** | String | "round \| square \| butt" | "round"| | **[`thickness`](#thickness)** | Number \| String | \>=0 as Number or percent value as String| "5%" | -| **[`lineMode`](#linemode)** | String | "normal \| out \| out-over \| in \| in-over \| top [offset]" | "normal 0" | +| **[`lineMode`](#linemode)** | String | "normal \| out \| out-over \| in \| in-over \| top \| bottom [offset]" | "normal 0" | | **[`emptyThickness`](#emptythickness)** | Number \| String | \>=0 as Number or percent value as String | "5%" | | **[`color`](#color)** | String \| Object | any color as String or Object to specify gradient (see details) | "#3f79ff" | | **[`colorFill`](#colorfill)** | String \| Object | same as `color` | "transparent" | diff --git a/package.json b/package.json index 21da364..75dd681 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vue-ellipse-progress", - "version": "1.0.2", + "version": "1.1.0-beta.2", "private": false, "description": "A Vue.js component to create beautiful animated circular progress bars", "main": "./dist/vue-ellipse-progress.umd.min.js", @@ -8,7 +8,7 @@ "serve": "vue-cli-service serve", "build": "vue-cli-service build --target lib --formats umd-min --name vue-ellipse-progress ./src/plugin.js", "lint": "vue-cli-service lint --fix", - "test": "vue-cli-service test:unit" + "test": "vue-cli-service test:unit --colors" }, "author": { "name": "Sergej Atamantschuk", diff --git a/src/App.vue b/src/App.vue index b224ad9..38c2d62 100644 --- a/src/App.vue +++ b/src/App.vue @@ -27,10 +27,6 @@ line - - Angle - - Determinate @@ -43,134 +39,54 @@ --> - - /hui - This is caption slot - + + + - - - - - - - - - - - Tasks - - - Update Tasks - - + :empty-thickness="100" + line="butt" + animation="rs 1000" + :dot="{ size: 100, backgroundColor: 'rgba(100,256,4,1)', width: '2px' }" + line-mode="in-over" + /> - diff --git a/src/components/Circle/HalfCircle.vue b/src/components/Circle/HalfCircle.vue index 547f625..0422cab 100644 --- a/src/components/Circle/HalfCircle.vue +++ b/src/components/Circle/HalfCircle.vue @@ -1,5 +1,12 @@ - + @@ -85,6 +95,4 @@ export default { g.ep-half-circle { transform-origin: 50% 50%; } -@import "~@/styles/animations.scss"; -@import "~@/styles/animationsUsage.scss"; diff --git a/src/components/Circle/circleMixin.js b/src/components/Circle/circleMixin.js index 1f15ce6..07fe2e2 100644 --- a/src/components/Circle/circleMixin.js +++ b/src/components/Circle/circleMixin.js @@ -1,5 +1,5 @@ import { isValidNumber } from "../../utils"; -import { animationParser, dashParser, lineModeParser } from "../optionsParser"; +import { animationParser, dashParser, lineModeParser, dotParser } from "../optionsParser"; import { simplifiedProps } from "../interface"; const wait = (ms = 400) => new Promise((resolve) => setTimeout(() => resolve(), ms)); @@ -29,6 +29,10 @@ export default { type: Number, required: false, }, + globalDot: { + type: [Number, String, Object], + required: false, + }, }, data: () => ({ isInitialized: false, @@ -49,7 +53,7 @@ export default { case "normal": return this.normalLineModeRadius; case "in": - return this.baseRadius - (this.computedEmptyThickness + offset); + return this.emptyRadius - (this.computedEmptyThickness / 2 + this.computedThickness / 2 + offset); case "out-over": if (this.computedEmptyThickness <= this.computedThickness) { return this.baseRadius; @@ -73,6 +77,17 @@ export default { switch (this.parsedLineMode.mode) { case "normal": return this.normalLineModeRadius; + case "in": + const dotSizeLimit = this.computedThickness / 2 + this.computedEmptyThickness + offset; + if (this.dotSize / 2 > dotSizeLimit) { + return this.emptyBaseRadius - (this.dotSize / 2 - dotSizeLimit); + } + return this.emptyBaseRadius; + case "in-over": + if (this.dotToThicknessDifference > 0) { + return this.emptyBaseRadius - this.dotToThicknessDifference / 2; + } + return this.emptyBaseRadius; case "out": return this.baseRadius - (this.computedThickness / 2 + this.computedEmptyThickness / 2 + offset); case "out-over": @@ -81,24 +96,24 @@ export default { } return this.emptyBaseRadius; case "bottom": - if (this.computedEmptyThickness < this.computedThickness / 2) { - return this.emptyBaseRadius - (this.computedThickness / 2 - this.computedEmptyThickness); + if (this.computedEmptyThickness < this.thicknessWithDot / 2) { + return this.emptyBaseRadius - (this.thicknessWithDot / 2 - this.computedEmptyThickness); } return this.emptyBaseRadius; case "top": - return this.emptyBaseRadius - this.computedThickness / 2; + return this.emptyBaseRadius - this.thicknessWithDot / 2; default: return this.emptyBaseRadius; } }, baseRadius() { - return this.size / 2 - this.computedThickness / 2; + return this.size / 2 - this.thicknessWithDot / 2; }, emptyBaseRadius() { return this.size / 2 - this.computedEmptyThickness / 2; }, normalLineModeRadius() { - if (this.computedThickness < this.computedEmptyThickness) { + if (this.thicknessWithDot < this.computedEmptyThickness) { return this.emptyBaseRadius; } return this.baseRadius; @@ -147,11 +162,16 @@ export default { computedThickness() { return this.calculateThickness(this.thickness.toString()); }, + + thicknessWithDot() { + return this.computedThickness < this.dotSize ? this.dotSize : this.computedThickness; + }, + computedGlobalThickness() { - return this.calculateThickness(this.globalThickness.toString()); + return this.calculateThickness(this.globalThickness); }, computedEmptyThickness() { - return this.calculateThickness(this.emptyThickness.toString()); + return this.calculateThickness(this.emptyThickness); }, computedAngle() { @@ -177,21 +197,41 @@ export default { previousCirclesThickness() { if (this.index === 0) return 0; const currentCircleGap = isValidNumber(this.gap) ? this.gap : this.globalGap; - const previousCirclesGap = this.data - .filter((data, i) => i < this.index) - .map((data, n) => { - const thickness = isValidNumber(data.thickness) ? data.thickness : this.computedGlobalThickness; - const gap = isValidNumber(data.gap) ? data.gap : this.globalGap; - return n > 0 ? thickness + gap : thickness; - }) - .reduce((acc, current) => acc + current); - return previousCirclesGap + currentCircleGap; + const previousCirclesThickness = []; + for (let i = 0; i < this.index; i++) { + const data = this.data[i]; + const dot = data.dot ? this.calculateThickness(dotParser(data.dot).size) : this.globalDotSize; + const thickness = isValidNumber(data.thickness) + ? this.calculateThickness(data.thickness) + : this.computedGlobalThickness; + const gap = isValidNumber(data.gap) ? data.gap : this.globalGap; + const completeThickness = Math.max(dot, thickness); + previousCirclesThickness.push(i > 0 ? completeThickness + gap : completeThickness); + } + return previousCirclesThickness.reduce((acc, current) => acc + current) + currentCircleGap; + }, + + parsedDot() { + return dotParser(this.dot); + }, + dotSize() { + return this.calculateThickness(this.parsedDot.size); + }, + dotColor() { + return this.parsedDot.color; + }, + dotToThicknessDifference() { + return this.dotSize - this.computedThickness; + }, + globalDotSize() { + return this.calculateThickness(dotParser(this.globalDot).size); }, styles() { return { strokeDashoffset: this.strokeDashOffset, - transition: this.animationDuration, + transitionDuration: this.animationDuration, + transitionTimingFunction: "ease-in-out", transformOrigin: this.transformOrigin, "--ep-circumference": this.circumference, "--ep-negative-circumference": this.getNegativeCircumference(), @@ -214,7 +254,7 @@ export default { calculateThickness(thickness) { const value = parseFloat(thickness); switch (true) { - case thickness.includes("%"): + case thickness.toString().includes("%"): return (value * this.size) / 100; default: return value; diff --git a/src/components/VueEllipseProgress.vue b/src/components/VueEllipseProgress.vue index 7161c2d..8f08442 100644 --- a/src/components/VueEllipseProgress.vue +++ b/src/components/VueEllipseProgress.vue @@ -16,6 +16,7 @@ :index="i" :globalThickness="thickness" :globalGap="gap" + :globalDot="dot" /> diff --git a/src/components/interface.js b/src/components/interface.js index e5843f8..3e587bf 100644 --- a/src/components/interface.js +++ b/src/components/interface.js @@ -83,6 +83,7 @@ const props = { validator: (value) => { const config = value.split(" "); const isValidType = ["default", "rs", "loop", "reverse", "bounce"].some((val) => val === config[0]); + // FIXME: config[1] const isValidDuration = config[0] ? parseFloat(config[1]) > 0 : true; const isValidDelay = config[2] ? parseFloat(config[2]) > 0 : true; @@ -141,6 +142,20 @@ const props = { required: false, default: false, }, + dot: { + type: [String, Number, Object], + required: false, + default: 0, + validator: (value) => { + if (typeof value === "object") { + if (value.size !== undefined) { + return !Number.isNaN(parseFloat(value.size)); + } + return false; + } + return !Number.isNaN(parseFloat(value)); + }, + }, }; const simplifiedProps = {}; diff --git a/src/components/optionsParser.js b/src/components/optionsParser.js index 07b388e..c71d0bd 100644 --- a/src/components/optionsParser.js +++ b/src/components/optionsParser.js @@ -29,4 +29,23 @@ const dashParser = (dash) => { }; }; -export { lineModeParser, animationParser, dashParser }; +const dotParser = (dot) => { + let dotSize = 0; + let dotColor = "white"; + let styles = {}; + if (typeof dot !== "object") { + const dotConfig = dot.toString().trim().split(" "); + dotSize = isValidNumber(dotConfig[0]) ? dotConfig[0] : 0; + dotColor = dotConfig[1] || "white"; + } else { + dotSize = dot.size || 0; + styles = dot; + } + return { + ...styles, + size: dotSize, + color: dotColor, + }; +}; + +export { lineModeParser, animationParser, dashParser, dotParser }; diff --git a/src/styles/animations.scss b/src/styles/animations.scss index e8c1479..17604f9 100644 --- a/src/styles/animations.scss +++ b/src/styles/animations.scss @@ -121,3 +121,68 @@ } } } + +@mixin ep-dot--init__rs ($dotStart, $dotEnd, $dot360) { + @keyframes ep-dot--init__rs { + 0% { + transform: rotate($dotStart); + } + 50% { + transform: rotate($dot360); + } + 100% { + transform: rotate($dotEnd); + } + } +} + +@keyframes ep-dot--init__loop { + 0% { + transform: rotate(var(--ep-dot-start)); + } + 33% { + transform: rotate(var(--ep-dot-360)); + } + 66% { + transform: rotate(var(--ep-dot-360)); + } + 100% { + transform: rotate(var(--ep-dot-loop-end)); + } +} + +@keyframes ep-dot--init__reverse { + 0% { + transform: rotate(var(--ep-dot-360)); + } + 50% { + transform: rotate(var(--ep-dot-360)); + } + 100% { + transform: rotate(var(--ep-dot-end)); + } +} + +@keyframes ep-dot--init__bounce { + 0% { + opacity: 0; + } + 90% { + opacity: 0; + } + 100% { + opacity: 1; + } +} + +@keyframes ep-dot--init__disabled { + 0% { + opacity: 0; + } + 90% { + opacity: 0; + } + 100% { + opacity: 1; + } +} diff --git a/src/styles/animationsUsage.scss b/src/styles/animationsUsage.scss index 4ad3b05..5417e71 100644 --- a/src/styles/animationsUsage.scss +++ b/src/styles/animationsUsage.scss @@ -1,22 +1,18 @@ .ep-circle--progress { + animation-timing-function: ease-in-out; &.animation__default { - animation-timing-function: ease-in-out; animation-name: ep-progress--init__default; } &.animation__rs { - animation-timing-function: ease-out; animation-name: ep-progress--init__rs; } &.animation__bounce { - animation-timing-function: ease-out; animation-name: ep-progress--init__bounce; } &.animation__reverse { - animation-timing-function: ease-out; animation-name: ep-progress--init__reverse; } &.animation__loop { - animation-timing-function: ease-out; animation-name: ep-progress--init__loop; } } @@ -45,6 +41,33 @@ } } +.ep-circle--progress__dot-container { + animation-timing-function: ease-in-out; + &.animation__rs { + animation-name: ep-dot--init__rs; + } + &.animation__bounce { + animation-fill-mode: forwards; + animation-name: ep-dot--init__disabled; + } + &.animation__reverse { + animation-name: ep-dot--init__reverse; + } + &.animation__loop { + animation-name: ep-dot--init__loop; + } + &.ep-half-circle-progress__dot{ + &.animation__bounce { + animation-fill-mode: forwards; + animation-name: ep-dot--init__disabled; + } + &.animation__loop { + animation-fill-mode: forwards; + animation-name: ep-dot--init__disabled; + } + } +} + @include ep-progress--init__default(var(--ep-stroke-offset), var(--ep-circumference)); @include ep-progress--init__rs(var(--ep-stroke-offset), var(--ep-circumference)); @include ep-progress--init__bounce( @@ -67,3 +90,4 @@ @include ep-half-progress--loading(var(--ep-loading-stroke-offset), var(--ep-circumference), var(--ep-negative-circumference)); @include ep-progress--loading__rotation(); +@include ep-dot--init__rs(var(--ep-dot-start), var(--ep-dot-end), var(--ep-dot-360)); diff --git a/tests/unit/circle/circle-animation.spec.js b/tests/unit/circle/circle-animation.spec.js index 492258f..bc9b3fe 100644 --- a/tests/unit/circle/circle-animation.spec.js +++ b/tests/unit/circle/circle-animation.spec.js @@ -2,9 +2,12 @@ import { expect } from "chai"; import { mount } from "@vue/test-utils"; import Vue from "vue"; import Circle from "../../../src/components/Circle/Circle.vue"; +import HalfCircle from "../../../src/components/Circle/HalfCircle.vue"; +import CircleContainer from "../../../src/components/Circle/CircleContainer.vue"; +import CircleDot from "../../../src/components/Circle/CircleDot.vue"; -const factory = (propsData) => { - return mount(Circle, { +const factory = (propsData, container = Circle) => { + return mount(container, { propsData: { index: 1, id: 2, @@ -15,110 +18,181 @@ const factory = (propsData) => { }); }; -export default () => { - describe("#animation", () => { - it("it parses the #animation property correctly", () => { - const wrapper = factory({ animation: "rs 2000 200" }); +const animationTypeTests = (container, circleClass, prefix = "circle |") => { + const wrapper = factory({}, container); + const circleProgressWrapper = wrapper.find(circleClass); + it(`${prefix} do not applies any animation type before delay`, () => { + expect(circleProgressWrapper.classes()) + .to.be.an("array") + .that.not.include([ + "animation__default", + "animation__bounce", + "animation__rs", + "animation__reverse", + "animation__loop", + ]); + }); + it(`${prefix} applies @default animation class by default`, (done) => { + setTimeout(() => { + expect(wrapper.vm.parsedAnimation.type).to.equal("default"); + expect(circleProgressWrapper.classes()).to.be.an("array").that.include("animation__default"); + done(); + }, 250); + }); + it(`${prefix} applies @bounce animation class correctly`, async () => { + wrapper.setProps({ animation: "bounce 500 500" }); + await Vue.nextTick(); + expect(circleProgressWrapper.classes()).to.include("animation__bounce"); + }); + it(`${prefix} applies @loop animation class correctly`, async () => { + wrapper.setProps({ animation: "loop 500 500" }); + await Vue.nextTick(); + expect(circleProgressWrapper.classes()).to.include("animation__loop"); + }); + it(`${prefix} applies @reverse animation class correctly`, async () => { + wrapper.setProps({ animation: "reverse 500 500" }); + await Vue.nextTick(); + expect(circleProgressWrapper.classes()).to.include("animation__reverse"); + }); + it(`${prefix} applies @rs animation class correctly`, async () => { + wrapper.setProps({ animation: "rs 500 500" }); + await Vue.nextTick(); + expect(circleProgressWrapper.classes()).to.include("animation__rs"); + }); +}; +const animationDurationTests = (container, circleClass, prefix = "circle | ") => { + it(`${prefix} applies default @1000 duration value as transition and animation duration`, () => { + const circleProgressWrapper = factory({}, container).find(circleClass); + + expect(circleProgressWrapper.element.style.transitionDuration).to.equal("1000ms"); + expect(circleProgressWrapper.element.style.animationDuration).to.equal("1000ms"); + }); + it(`${prefix} applies provided duration value as transition and animation duration`, () => { + const circleProgressWrapper = factory({ animation: "rs 500" }, container).find(circleClass); + + expect(circleProgressWrapper.element.style.transitionDuration).to.equal("500ms"); + expect(circleProgressWrapper.element.style.animationDuration).to.equal("500ms"); + }); + it(`${prefix} applies @0 duration value as transition and animation duration`, () => { + const circleProgressWrapper = factory({ animation: "rs 0" }, container).find(circleClass); + + expect(circleProgressWrapper.element.style.transitionDuration).to.equal("0ms"); + expect(circleProgressWrapper.element.style.animationDuration).to.equal("0ms"); + }); +}; +const animationDelayTests = (container, circleClass, prefix = "circle | ") => { + it(`${prefix} applies default @400 delay value as initial animation delay`, () => { + expect(factory({}, container).vm.parsedAnimation.delay).to.equal(400); + }); + it(`${prefix} applies @0 delay value as animation-delay`, () => { + expect(factory({ animation: "rs 0 0" }, container).vm.parsedAnimation.delay).to.equal(0); + }); + + const progress = 60; + const size = 200; + const thickness = 4; - expect(wrapper.vm.parsedAnimation.type).to.equal("rs"); - expect(wrapper.vm.parsedAnimation.duration).to.equal(2000); - expect(wrapper.vm.parsedAnimation.delay).to.equal(200); + const isHalfCircle = prefix.includes("half"); + + const radius = size / 2 - thickness / 2; + let circumference = radius * 2 * Math.PI; + circumference = isHalfCircle ? circumference / 2 : circumference; + const expectedOffset = circumference - (progress / 100) * circumference; + + it(`${prefix} don not applies progress before delay`, () => { + const wrapper = factory( + { progress, size, thickness, emptyThickness: thickness, animation: "rs 500 100" }, + container + ); + const circleProgressWrapper = wrapper.find(circleClass); + expect(wrapper.vm.strokeDashOffset).to.equal(circumference); + expect(circleProgressWrapper.element.style.strokeDashoffset).to.equal(`${circumference}`); + }); + it(`${prefix} applies the progress after delay`, (done) => { + const wrapper = factory( + { progress, size, thickness, emptyThickness: thickness, animation: "rs 500 100" }, + container + ); + const circleProgressWrapper = wrapper.find(circleClass); + setTimeout(() => { + expect(wrapper.vm.strokeDashOffset).to.equal(expectedOffset); + expect(circleProgressWrapper.element.style.strokeDashoffset).to.equal(`${expectedOffset}`); + done(); + }, 150); + }); +}; + +describe("#animation", () => { + const circleContainerWrapper = factory({ progress: 50, dot: 5, animation: "rs 500 5" }, CircleContainer); + const circleDotWrapper = circleContainerWrapper.find(CircleDot); + + it("it parses the #animation property correctly", () => { + const wrapper = factory({ animation: "rs 2000 200" }); + + expect(wrapper.vm.parsedAnimation.type).to.equal("rs"); + expect(wrapper.vm.parsedAnimation.duration).to.equal(2000); + expect(wrapper.vm.parsedAnimation.delay).to.equal(200); + }); + describe("#animation.type", () => { + animationTypeTests(Circle, "circle.ep-circle--progress"); + animationTypeTests(HalfCircle, "path.ep-half-circle--progress", "half circle |"); + + it("circle dot | applies animation class correctly", (done) => { + setTimeout(() => { + expect(circleDotWrapper.classes()).to.be.an("array").that.include("animation__rs"); + done(); + }, 5); }); - describe("#animation.type", () => { - const wrapper = factory(); - const circleProgressWrapper = wrapper.find("circle.ep-circle--progress"); - it("do not applies any animation type before delay", () => { - expect(circleProgressWrapper.classes()) - .to.be.an("array") - .that.not.include([ - "animation__default", - "animation__bounce", - "animation__rs", - "animation__reverse", - "animation__loop", - ]); - }); - it("applies @default animation class by default", (done) => { - setTimeout(() => { - expect(wrapper.vm.parsedAnimation.type).to.equal("default"); - expect(circleProgressWrapper.classes()).to.be.an("array").that.include("animation__default"); - done(); - }, 250); - }); - it("applies @bounce animation class correctly", async () => { - wrapper.setProps({ animation: "bounce 500 500" }); - await Vue.nextTick(); - expect(circleProgressWrapper.classes()).to.include("animation__bounce"); - }); - it("applies @loop animation class correctly", async () => { - wrapper.setProps({ animation: "loop 500 500" }); - await Vue.nextTick(); - expect(circleProgressWrapper.classes()).to.include("animation__loop"); - }); - it("applies @reverse animation class correctly", async () => { - wrapper.setProps({ animation: "reverse 500 500" }); - await Vue.nextTick(); - expect(circleProgressWrapper.classes()).to.include("animation__reverse"); - }); - it("applies @rs animation class correctly", async () => { - wrapper.setProps({ animation: "rs 500 500" }); - await Vue.nextTick(); - expect(circleProgressWrapper.classes()).to.include("animation__rs"); - }); + /* it("circle dot | disables initial animation for @bounce/@loop types", (done) => { + const wrapper = factory({ progress: 50, dot: 5, animation: "bounce 500 0" }, CircleContainer); + const cDWrapper = wrapper.find(CircleDot); + setTimeout(() => { + expect(cDWrapper.classes()).to.be.an("array").that.include("animation__bounce"); + const { animationName } = getComputedStyle(cDWrapper.element); + expect(animationName).to.equal("ep-dot--init__disabled"); + done(); + }, 10); + }); */ + }); + + describe("#animation.duration", () => { + animationDurationTests(Circle, "circle.ep-circle--progress"); + animationDurationTests(HalfCircle, "path.ep-half-circle--progress", "half circle |"); + + it(`circle dot | applies duration value as transition and animation duration`, () => { + expect(circleDotWrapper.element.style.transitionDuration).to.equal("500ms"); + expect(circleDotWrapper.element.style.animationDuration).to.equal("500ms"); }); - describe("#animation.duration", () => { - it("applies default @1000 duration value as transition and animation duration", () => { - const circleProgressWrapper = factory().find("circle.ep-circle--progress"); - - expect(circleProgressWrapper.element.style.transition).to.include("1000ms"); - expect(circleProgressWrapper.element.style.animationDuration).to.equal("1000ms"); - }); - it("applies provided duration value as transition and animation duration", () => { - const wrapper = factory({ animation: "rs 500" }); - const circleProgressWrapper = wrapper.find("circle.ep-circle--progress"); - - expect(circleProgressWrapper.element.style.transition).to.equal("500ms"); - expect(circleProgressWrapper.element.style.animationDuration).to.equal("500ms"); - }); - it("applies @0 duration value as transition and animation duration", () => { - const wrapper = factory({ animation: "rs 0" }); - const circleProgressWrapper = wrapper.find("circle.ep-circle--progress"); - - expect(circleProgressWrapper.element.style.transition).to.equal("0ms"); - expect(circleProgressWrapper.element.style.animationDuration).to.equal("0ms"); - }); + }); + describe("#animation.delay", () => { + animationDelayTests(Circle, "circle.ep-circle--progress"); + animationDelayTests(HalfCircle, "path.ep-half-circle--progress", "half circle |"); + + const progress = 50; + const wrapper = factory({ dot: 5, animation: "rs 500 50" }, CircleContainer); + const cdWrapper = wrapper.find(CircleDot); + const startRotation = wrapper.props("angle") + 90; + + it(`circle dot | do not applies any animation type before delay`, () => { + expect(cdWrapper.classes()) + .to.be.an("array") + .that.not.include([ + "animation__default", + "animation__bounce", + "animation__rs", + "animation__reverse", + "animation__loop", + ]); }); - describe("#animation.delay", () => { - it("applies default @400 delay value as initial animation delay", () => { - expect(factory().vm.parsedAnimation.delay).to.equal(400); - }); - it("applies @0 delay value as animation-delay", () => { - expect(factory({ animation: "rs 0 0" }).vm.parsedAnimation.delay).to.equal(0); - }); - - const progress = 60; - const size = 200; - const thickness = 4; - - const radius = size / 2 - thickness / 2; - const circumference = radius * 2 * Math.PI; - const expectedOffset = circumference - (progress / 100) * circumference; - - it("don not applies progress before delay", () => { - const wrapper = factory({ progress, size, thickness, emptyThickness: thickness, animation: "rs 500 100" }); - const circleProgressWrapper = wrapper.find("circle.ep-circle--progress"); - expect(wrapper.vm.strokeDashOffset).to.equal(circumference); - expect(circleProgressWrapper.element.style.strokeDashoffset).to.equal(`${circumference}`); - }); - it("applies the progress after delay", (done) => { - const wrapper = factory({ progress, size, thickness, emptyThickness: thickness, animation: "rs 500 100" }); - const circleProgressWrapper = wrapper.find("circle.ep-circle--progress"); - setTimeout(() => { - expect(wrapper.vm.strokeDashOffset).to.equal(expectedOffset); - expect(circleProgressWrapper.element.style.strokeDashoffset).to.equal(`${expectedOffset}`); - done(); - }, 150); - }); + /* it(`circle dot | do not applies rotation before delay`, () => { + expect(cdWrapper.element.style.transform).to.equal(`rotate(${startRotation}deg)`); + }); */ + it(`circle dot | applies rotation after delay`, (done) => { + const endRotation = startRotation + (progress * 360) / 100; + setTimeout(() => { + expect(cdWrapper.element.style.transform).to.equal(`rotate(${endRotation}deg)`); + done(); + }, 50); }); }); -}; +}); diff --git a/tests/unit/circle/circle-colors.spec.js b/tests/unit/circle/circle-colors.spec.js index e8ea0c9..5c409bf 100644 --- a/tests/unit/circle/circle-colors.spec.js +++ b/tests/unit/circle/circle-colors.spec.js @@ -30,137 +30,135 @@ const gradientColor = { ], }; -export default () => { - describe("#color", () => { - describe("applies color as string", () => { - const color = "#ff0020"; - const wrapper = factory({ color }); - const circleProgressWrapper = wrapper.find("circle.ep-circle--progress"); +describe("#color", () => { + describe("applies color as string", () => { + const color = "#ff0020"; + const wrapper = factory({ color }); + const circleProgressWrapper = wrapper.find("circle.ep-circle--progress"); - it("do not recognize gradient colors", () => { - expect(wrapper.vm.isColorGradient).to.be.false; - }); + it("do not recognize gradient colors", () => { + expect(wrapper.vm.isColorGradient).to.be.false; + }); - it("applies color correctly to SVG stroke", () => { - expect(circleProgressWrapper.element.getAttribute("stroke")).to.equal(`${color}`); - }); - }); - describe("applies gradient color correctly", () => { - const wrapper = factory({ color: gradientColor }); - const circleProgressWrapper = wrapper.find("circle.ep-circle--progress"); - const id = wrapper.vm._uid; + it("applies color correctly to SVG stroke", () => { + expect(circleProgressWrapper.element.getAttribute("stroke")).to.equal(`${color}`); + }); + }); + describe("applies gradient color correctly", () => { + const wrapper = factory({ color: gradientColor }); + const circleProgressWrapper = wrapper.find("circle.ep-circle--progress"); + const id = wrapper.vm._uid; - it("recognizes gradient colors", () => { - expect(wrapper.vm.isColorGradient).to.be.true; - }); - it("renders Gradient component", () => { - expect(wrapper.contains(Gradient)).to.be.true; - }); - it("applies gradient URL to SVG stroke", () => { - expect(circleProgressWrapper.element.getAttribute("stroke")).to.equal(`url(#ep-progress-gradient-${id})`); - }); - it("renders corresponding amount of stop colors SVG elements", () => { - expect(wrapper.findAll("stop").length).to.equal(gradientColor.colors.length); - }); + it("recognizes gradient colors", () => { + expect(wrapper.vm.isColorGradient).to.be.true; + }); + it("renders Gradient component", () => { + expect(wrapper.contains(Gradient)).to.be.true; + }); + it("applies gradient URL to SVG stroke", () => { + expect(circleProgressWrapper.element.getAttribute("stroke")).to.equal(`url(#ep-progress-gradient-${id})`); + }); + it("renders corresponding amount of stop colors SVG elements", () => { + expect(wrapper.findAll("stop").length).to.equal(gradientColor.colors.length); }); }); - describe("#emptyColor", () => { - describe("applies color as string", () => { - const emptyColor = "#a617ff"; - const wrapper = factory({ emptyColor }); - const emptyCircleWrapper = wrapper.find("circle.ep-circle--empty"); +}); +describe("#emptyColor", () => { + describe("applies color as string", () => { + const emptyColor = "#a617ff"; + const wrapper = factory({ emptyColor }); + const emptyCircleWrapper = wrapper.find("circle.ep-circle--empty"); - it("do not recognize gradient colors", () => { - expect(wrapper.vm.isEmptyColorGradient).to.be.false; - }); + it("do not recognize gradient colors", () => { + expect(wrapper.vm.isEmptyColorGradient).to.be.false; + }); - it("applies color correctly to SVG stroke", () => { - expect(emptyCircleWrapper.element.getAttribute("stroke")).to.equal(`${emptyColor}`); - }); - }); - describe("applies gradient color correctly", () => { - const wrapper = factory({ emptyColor: gradientColor }); - const circleProgressWrapper = wrapper.find("circle.ep-circle--empty"); - const id = wrapper.vm._uid; + it("applies color correctly to SVG stroke", () => { + expect(emptyCircleWrapper.element.getAttribute("stroke")).to.equal(`${emptyColor}`); + }); + }); + describe("applies gradient color correctly", () => { + const wrapper = factory({ emptyColor: gradientColor }); + const circleProgressWrapper = wrapper.find("circle.ep-circle--empty"); + const id = wrapper.vm._uid; - it("recognizes gradient colors", () => { - expect(wrapper.vm.isEmptyColorGradient).to.be.true; - }); - it("renders Gradient component", () => { - expect(wrapper.contains(Gradient)).to.be.true; - }); - it("applies gradient URL to SVG stroke", () => { - expect(circleProgressWrapper.element.getAttribute("stroke")).to.equal(`url(#ep-empty-gradient-${id})`); - }); - it("renders corresponding amount of stop colors SVG elements", () => { - expect(wrapper.findAll("stop").length).to.equal(gradientColor.colors.length); - }); + it("recognizes gradient colors", () => { + expect(wrapper.vm.isEmptyColorGradient).to.be.true; + }); + it("renders Gradient component", () => { + expect(wrapper.contains(Gradient)).to.be.true; + }); + it("applies gradient URL to SVG stroke", () => { + expect(circleProgressWrapper.element.getAttribute("stroke")).to.equal(`url(#ep-empty-gradient-${id})`); + }); + it("renders corresponding amount of stop colors SVG elements", () => { + expect(wrapper.findAll("stop").length).to.equal(gradientColor.colors.length); }); }); - describe("#colorFill", () => { - describe("applies color as string", () => { - const colorFill = "#fff149"; - const wrapper = factory({ colorFill }); - const circleProgressWrapper = wrapper.find("circle.ep-circle--progress"); +}); +describe("#colorFill", () => { + describe("applies color as string", () => { + const colorFill = "#fff149"; + const wrapper = factory({ colorFill }); + const circleProgressWrapper = wrapper.find("circle.ep-circle--progress"); - it("do not recognize gradient colors", () => { - expect(wrapper.vm.isColorGradient).to.be.false; - }); + it("do not recognize gradient colors", () => { + expect(wrapper.vm.isColorGradient).to.be.false; + }); - it("applies color correctly to SVG fill", () => { - expect(circleProgressWrapper.element.getAttribute("fill")).to.equal(`${colorFill}`); - }); - }); - describe("applies gradient color correctly", () => { - const wrapper = factory({ colorFill: gradientColor }); - const circleProgressWrapper = wrapper.find("circle.ep-circle--progress"); - const id = wrapper.vm._uid; + it("applies color correctly to SVG fill", () => { + expect(circleProgressWrapper.element.getAttribute("fill")).to.equal(`${colorFill}`); + }); + }); + describe("applies gradient color correctly", () => { + const wrapper = factory({ colorFill: gradientColor }); + const circleProgressWrapper = wrapper.find("circle.ep-circle--progress"); + const id = wrapper.vm._uid; - it("recognizes gradient colors", () => { - expect(wrapper.vm.isColorFillGradient).to.be.true; - }); - it("renders Gradient component", () => { - expect(wrapper.contains(Gradient)).to.be.true; - }); - it("applies gradient URL to SVG fill", () => { - expect(circleProgressWrapper.element.getAttribute("fill")).to.equal(`url(#ep-progress-fill-gradient-${id})`); - }); - it("renders corresponding amount of stop colors SVG elements", () => { - expect(wrapper.findAll("stop").length).to.equal(gradientColor.colors.length); - }); + it("recognizes gradient colors", () => { + expect(wrapper.vm.isColorFillGradient).to.be.true; + }); + it("renders Gradient component", () => { + expect(wrapper.contains(Gradient)).to.be.true; + }); + it("applies gradient URL to SVG fill", () => { + expect(circleProgressWrapper.element.getAttribute("fill")).to.equal(`url(#ep-progress-fill-gradient-${id})`); + }); + it("renders corresponding amount of stop colors SVG elements", () => { + expect(wrapper.findAll("stop").length).to.equal(gradientColor.colors.length); }); }); - describe("#emptyColorFill", () => { - describe("applies color as string", () => { - const emptyColorFill = "#3f79ff"; - const wrapper = factory({ emptyColorFill }); - const emptyCircleWrapper = wrapper.find("circle.ep-circle--empty"); +}); +describe("#emptyColorFill", () => { + describe("applies color as string", () => { + const emptyColorFill = "#3f79ff"; + const wrapper = factory({ emptyColorFill }); + const emptyCircleWrapper = wrapper.find("circle.ep-circle--empty"); - it("do not recognize gradient colors", () => { - expect(wrapper.vm.isEmptyColorGradient).to.be.false; - }); + it("do not recognize gradient colors", () => { + expect(wrapper.vm.isEmptyColorGradient).to.be.false; + }); - it("applies color correctly to SVG fill", () => { - expect(emptyCircleWrapper.element.getAttribute("fill")).to.equal(`${emptyColorFill}`); - }); - }); - describe("applies gradient color correctly", () => { - const wrapper = factory({ emptyColorFill: gradientColor }); - const emptyCircleWrapper = wrapper.find("circle.ep-circle--empty"); - const id = wrapper.vm._uid; + it("applies color correctly to SVG fill", () => { + expect(emptyCircleWrapper.element.getAttribute("fill")).to.equal(`${emptyColorFill}`); + }); + }); + describe("applies gradient color correctly", () => { + const wrapper = factory({ emptyColorFill: gradientColor }); + const emptyCircleWrapper = wrapper.find("circle.ep-circle--empty"); + const id = wrapper.vm._uid; - it("recognizes gradient colors", () => { - expect(wrapper.vm.isEmptyColorFillGradient).to.be.true; - }); - it("renders Gradient component", () => { - expect(wrapper.contains(Gradient)).to.be.true; - }); - it("applies gradient URL to SVG fill", () => { - expect(emptyCircleWrapper.element.getAttribute("fill")).to.equal(`url(#ep-empty-fill-gradient-${id})`); - }); - it("renders corresponding amount of stop colors SVG elements", () => { - expect(wrapper.findAll("stop").length).to.equal(gradientColor.colors.length); - }); + it("recognizes gradient colors", () => { + expect(wrapper.vm.isEmptyColorFillGradient).to.be.true; + }); + it("renders Gradient component", () => { + expect(wrapper.contains(Gradient)).to.be.true; + }); + it("applies gradient URL to SVG fill", () => { + expect(emptyCircleWrapper.element.getAttribute("fill")).to.equal(`url(#ep-empty-fill-gradient-${id})`); + }); + it("renders corresponding amount of stop colors SVG elements", () => { + expect(wrapper.findAll("stop").length).to.equal(gradientColor.colors.length); }); }); -}; +}); diff --git a/tests/unit/circle/circle-line.spec.js b/tests/unit/circle/circle-line.spec.js index 52f3797..3396a5e 100644 --- a/tests/unit/circle/circle-line.spec.js +++ b/tests/unit/circle/circle-line.spec.js @@ -31,249 +31,247 @@ const factory = (propsData) => { }); }; -export default () => { - describe("#line", () => { - it("renders line type correctly", async () => { - let line = "round"; - - const wrapper = factory({ line }); - const circleProgressWrapper = wrapper.find("circle.ep-circle--progress"); - expect(circleProgressWrapper.element.getAttribute("stroke-linecap")).to.equal(`${line}`); - - line = "butt"; - wrapper.setProps({ line }); - await Vue.nextTick(); - expect(circleProgressWrapper.element.getAttribute("stroke-linecap")).to.equal(`${line}`); - - line = "square"; - wrapper.setProps({ line }); - await Vue.nextTick(); - expect(circleProgressWrapper.element.getAttribute("stroke-linecap")).to.equal(`${line}`); - }); +describe("#line", () => { + it("renders line type correctly", async () => { + let line = "round"; + + const wrapper = factory({ line }); + const circleProgressWrapper = wrapper.find("circle.ep-circle--progress"); + expect(circleProgressWrapper.element.getAttribute("stroke-linecap")).to.equal(`${line}`); + + line = "butt"; + wrapper.setProps({ line }); + await Vue.nextTick(); + expect(circleProgressWrapper.element.getAttribute("stroke-linecap")).to.equal(`${line}`); + + line = "square"; + wrapper.setProps({ line }); + await Vue.nextTick(); + expect(circleProgressWrapper.element.getAttribute("stroke-linecap")).to.equal(`${line}`); }); - describe("#lineMode", () => { - it("it parses the #lineMode property correctly", () => { - const wrapper = factory({ lineMode: "normal 10" }); +}); +describe("#lineMode", () => { + it("it parses the #lineMode property correctly", () => { + const wrapper = factory({ lineMode: "normal 10" }); - expect(wrapper.vm.parsedLineMode.mode).to.equal("normal"); - expect(wrapper.vm.parsedLineMode.offset).to.equal(10); - }); - it("it applies default value correctly", () => { - const wrapper = factory({ lineMode: "normal 10" }); + expect(wrapper.vm.parsedLineMode.mode).to.equal("normal"); + expect(wrapper.vm.parsedLineMode.offset).to.equal(10); + }); + it("it applies default value correctly", () => { + const wrapper = factory({ lineMode: "normal 10" }); - expect(wrapper.vm.parsedLineMode.mode).to.equal("normal"); - expect(wrapper.vm.parsedLineMode.offset).to.equal(10); - }); - describe("#lineMode.mode", () => { - describe("#lineMode.mode.normal", () => { - let thickness = 20; - let emptyThickness = 10; - const wrapper = factory({ - thickness, - emptyThickness, - lineMode: "normal", - animation: "default 0 0", - }); + expect(wrapper.vm.parsedLineMode.mode).to.equal("normal"); + expect(wrapper.vm.parsedLineMode.offset).to.equal(10); + }); + describe("#lineMode.mode", () => { + describe("#lineMode.mode.normal", () => { + let thickness = 20; + let emptyThickness = 10; + const wrapper = factory({ + thickness, + emptyThickness, + lineMode: "normal", + animation: "default 0 0", + }); - describe("radius of the circles does not exceed the size and aligns properly in relation to each other", () => { - it("in case #thickness >= #emptyThickness", async () => { - let expectedProgressCircleRadius = baseRadius - thickness / 2; - let expectedEmptyCircleRadius = expectedProgressCircleRadius; - compareRadiusValues(wrapper, expectedProgressCircleRadius, expectedEmptyCircleRadius); - - thickness = emptyThickness = 10; - - wrapper.setProps({ thickness, emptyThickness }); - await Vue.nextTick(); - - expectedProgressCircleRadius = baseRadius - thickness / 2; - expectedEmptyCircleRadius = expectedProgressCircleRadius; - compareRadiusValues(wrapper, expectedProgressCircleRadius, expectedEmptyCircleRadius); - }); - it("in case #thickness >= #emptyThickness and #lineMode.offset = 10", async () => { - // offset must be ignored in this mode - wrapper.setProps({ lineMode: "normal 10" }); - await Vue.nextTick(); - - let expectedProgressCircleRadius = baseRadius - thickness / 2; - let expectedEmptyCircleRadius = expectedProgressCircleRadius; - compareRadiusValues(wrapper, expectedProgressCircleRadius, expectedEmptyCircleRadius); - - thickness = emptyThickness = 10; - - wrapper.setProps({ thickness, emptyThickness }); - await Vue.nextTick(); - - expectedProgressCircleRadius = baseRadius - thickness / 2; - expectedEmptyCircleRadius = expectedProgressCircleRadius; - compareRadiusValues(wrapper, expectedProgressCircleRadius, expectedEmptyCircleRadius); - }); - it("in case #thickness < #emptyThickness", async () => { - thickness = 10; - emptyThickness = 20; - - wrapper.setProps({ thickness, emptyThickness }); - await Vue.nextTick(); - - const expectedEmptyCircleRadius = baseRadius - emptyThickness / 2; - const expectedProgressCircleRadius = expectedEmptyCircleRadius; - compareRadiusValues(wrapper, expectedProgressCircleRadius, expectedEmptyCircleRadius); - }); - it("in case #thickness < #emptyThickness and #lineMode.offset = 10", async () => { - // offset must be ignored in this mode - thickness = 10; - emptyThickness = 20; - - wrapper.setProps({ thickness, emptyThickness, lineMode: "normal 10" }); - await Vue.nextTick(); - - const expectedEmptyCircleRadius = baseRadius - emptyThickness / 2; - const expectedProgressCircleRadius = expectedEmptyCircleRadius; - compareRadiusValues(wrapper, expectedProgressCircleRadius, expectedEmptyCircleRadius); - }); + describe("radius of the circles does not exceed the size and aligns properly in relation to each other", () => { + it("in case #thickness >= #emptyThickness", async () => { + let expectedProgressCircleRadius = baseRadius - thickness / 2; + let expectedEmptyCircleRadius = expectedProgressCircleRadius; + compareRadiusValues(wrapper, expectedProgressCircleRadius, expectedEmptyCircleRadius); + + thickness = emptyThickness = 10; + + wrapper.setProps({ thickness, emptyThickness }); + await Vue.nextTick(); + + expectedProgressCircleRadius = baseRadius - thickness / 2; + expectedEmptyCircleRadius = expectedProgressCircleRadius; + compareRadiusValues(wrapper, expectedProgressCircleRadius, expectedEmptyCircleRadius); }); - }); - describe("#lineMode.mode.in", () => { - const offset = 10; - const thickness = 20; - const emptyThickness = 10; - const wrapper = factory({ - thickness, - emptyThickness, - lineMode: `in ${offset}`, + it("in case #thickness >= #emptyThickness and #lineMode.offset = 10", async () => { + // offset must be ignored in this mode + wrapper.setProps({ lineMode: "normal 10" }); + await Vue.nextTick(); + + let expectedProgressCircleRadius = baseRadius - thickness / 2; + let expectedEmptyCircleRadius = expectedProgressCircleRadius; + compareRadiusValues(wrapper, expectedProgressCircleRadius, expectedEmptyCircleRadius); + + thickness = emptyThickness = 10; + + wrapper.setProps({ thickness, emptyThickness }); + await Vue.nextTick(); + + expectedProgressCircleRadius = baseRadius - thickness / 2; + expectedEmptyCircleRadius = expectedProgressCircleRadius; + compareRadiusValues(wrapper, expectedProgressCircleRadius, expectedEmptyCircleRadius); }); + it("in case #thickness < #emptyThickness", async () => { + thickness = 10; + emptyThickness = 20; + + wrapper.setProps({ thickness, emptyThickness }); + await Vue.nextTick(); - it("circles does not exceed the size and aligns properly in relation to each other", () => { const expectedEmptyCircleRadius = baseRadius - emptyThickness / 2; - const expectedProgressCircleRadius = expectedEmptyCircleRadius - emptyThickness / 2 - thickness / 2 - offset; + const expectedProgressCircleRadius = expectedEmptyCircleRadius; compareRadiusValues(wrapper, expectedProgressCircleRadius, expectedEmptyCircleRadius); }); - }); - describe("#lineMode.mode.in-over", () => { - const offset = 10; // must be ignored in this mode - const thickness = 20; - const emptyThickness = 10; - const wrapper = factory({ - thickness, - emptyThickness, - lineMode: `in-over ${offset}`, - }); + it("in case #thickness < #emptyThickness and #lineMode.offset = 10", async () => { + // offset must be ignored in this mode + thickness = 10; + emptyThickness = 20; + + wrapper.setProps({ thickness, emptyThickness, lineMode: "normal 10" }); + await Vue.nextTick(); - it("circles does not exceed the size and aligns properly in relation to each other", () => { const expectedEmptyCircleRadius = baseRadius - emptyThickness / 2; - const expectedProgressCircleRadius = baseRadius - thickness / 2; + const expectedProgressCircleRadius = expectedEmptyCircleRadius; compareRadiusValues(wrapper, expectedProgressCircleRadius, expectedEmptyCircleRadius); }); }); - describe("#lineMode.mode.out", () => { - const offset = 10; - const thickness = 10; - const emptyThickness = 10; - const wrapper = factory({ - progress, - thickness, - emptyThickness, - lineMode: `out ${offset}`, - }); + }); + describe("#lineMode.mode.in", () => { + const offset = 10; + const thickness = 20; + const emptyThickness = 10; + const wrapper = factory({ + thickness, + emptyThickness, + lineMode: `in ${offset}`, + }); - it("circles does not exceed the size and aligns properly in relation to each other", () => { - const expectedProgressCircleRadius = baseRadius - emptyThickness / 2; - const expectedEmptyCircleRadius = expectedProgressCircleRadius - emptyThickness / 2 - thickness / 2 - offset; - compareRadiusValues(wrapper, expectedProgressCircleRadius, expectedEmptyCircleRadius); - }); + it("circles does not exceed the size and aligns properly in relation to each other", () => { + const expectedEmptyCircleRadius = baseRadius - emptyThickness / 2; + const expectedProgressCircleRadius = expectedEmptyCircleRadius - emptyThickness / 2 - thickness / 2 - offset; + compareRadiusValues(wrapper, expectedProgressCircleRadius, expectedEmptyCircleRadius); + }); + }); + describe("#lineMode.mode.in-over", () => { + const offset = 10; // must be ignored in this mode + const thickness = 20; + const emptyThickness = 10; + const wrapper = factory({ + thickness, + emptyThickness, + lineMode: `in-over ${offset}`, }); - describe("#lineMode.mode.out-over", () => { - const offset = 10; // must be ignored in this mode - let thickness = 20; - let emptyThickness = 10; - const wrapper = factory({ - thickness, - emptyThickness, - lineMode: `out-over ${offset}`, - }); - describe("radius of the circles does not exceed the size and aligns properly in relation to each other", () => { - it("in case #thickness >= #emptyThickness", async () => { - let expectedProgressCircleRadius = baseRadius - thickness / 2; - let expectedEmptyCircleRadius = expectedProgressCircleRadius - thickness / 2 + emptyThickness / 2; - compareRadiusValues(wrapper, expectedProgressCircleRadius, expectedEmptyCircleRadius); + it("circles does not exceed the size and aligns properly in relation to each other", () => { + const expectedEmptyCircleRadius = baseRadius - emptyThickness / 2; + const expectedProgressCircleRadius = baseRadius - thickness / 2; + compareRadiusValues(wrapper, expectedProgressCircleRadius, expectedEmptyCircleRadius); + }); + }); + describe("#lineMode.mode.out", () => { + const offset = 10; + const thickness = 10; + const emptyThickness = 10; + const wrapper = factory({ + progress, + thickness, + emptyThickness, + lineMode: `out ${offset}`, + }); - thickness = emptyThickness = 10; + it("circles does not exceed the size and aligns properly in relation to each other", () => { + const expectedProgressCircleRadius = baseRadius - emptyThickness / 2; + const expectedEmptyCircleRadius = expectedProgressCircleRadius - emptyThickness / 2 - thickness / 2 - offset; + compareRadiusValues(wrapper, expectedProgressCircleRadius, expectedEmptyCircleRadius); + }); + }); + describe("#lineMode.mode.out-over", () => { + const offset = 10; // must be ignored in this mode + let thickness = 20; + let emptyThickness = 10; + const wrapper = factory({ + thickness, + emptyThickness, + lineMode: `out-over ${offset}`, + }); - wrapper.setProps({ thickness, emptyThickness }); - await Vue.nextTick(); + describe("radius of the circles does not exceed the size and aligns properly in relation to each other", () => { + it("in case #thickness >= #emptyThickness", async () => { + let expectedProgressCircleRadius = baseRadius - thickness / 2; + let expectedEmptyCircleRadius = expectedProgressCircleRadius - thickness / 2 + emptyThickness / 2; + compareRadiusValues(wrapper, expectedProgressCircleRadius, expectedEmptyCircleRadius); - expectedProgressCircleRadius = baseRadius - thickness / 2; - expectedEmptyCircleRadius = expectedProgressCircleRadius; - compareRadiusValues(wrapper, expectedProgressCircleRadius, expectedEmptyCircleRadius); - }); - it("in case #thickness < #emptyThickness", async () => { - thickness = 10; - emptyThickness = 20; + thickness = emptyThickness = 10; - wrapper.setProps({ thickness, emptyThickness }); - await Vue.nextTick(); + wrapper.setProps({ thickness, emptyThickness }); + await Vue.nextTick(); - const expectedEmptyCircleRadius = baseRadius - emptyThickness / 2; - const expectedProgressCircleRadius = expectedEmptyCircleRadius - emptyThickness / 2 + thickness / 2; - compareRadiusValues(wrapper, expectedProgressCircleRadius, expectedEmptyCircleRadius); - }); - }); - }); - describe("#lineMode.mode.top", () => { - const offset = 10; // must be ignored in this mode - const thickness = 20; - const emptyThickness = 20; - const wrapper = factory({ - thickness, - emptyThickness, - lineMode: `top ${offset}`, + expectedProgressCircleRadius = baseRadius - thickness / 2; + expectedEmptyCircleRadius = expectedProgressCircleRadius; + compareRadiusValues(wrapper, expectedProgressCircleRadius, expectedEmptyCircleRadius); }); + it("in case #thickness < #emptyThickness", async () => { + thickness = 10; + emptyThickness = 20; - it("circles does not exceed the size and aligns properly in relation to each other", () => { - const expectedProgressCircleRadius = baseRadius - thickness / 2; - const expectedEmptyCircleRadius = expectedProgressCircleRadius - emptyThickness / 2; + wrapper.setProps({ thickness, emptyThickness }); + await Vue.nextTick(); + + const expectedEmptyCircleRadius = baseRadius - emptyThickness / 2; + const expectedProgressCircleRadius = expectedEmptyCircleRadius - emptyThickness / 2 + thickness / 2; compareRadiusValues(wrapper, expectedProgressCircleRadius, expectedEmptyCircleRadius); }); }); - describe("#lineMode.mode.bottom", () => { - const offset = 10; // must be ignored in this mode - let thickness = 40; - let emptyThickness = 10; - const wrapper = factory({ - thickness, - emptyThickness, - lineMode: `bottom ${offset}`, - }); + }); + describe("#lineMode.mode.top", () => { + const offset = 10; // must be ignored in this mode + const thickness = 20; + const emptyThickness = 20; + const wrapper = factory({ + thickness, + emptyThickness, + lineMode: `top ${offset}`, + }); + + it("circles does not exceed the size and aligns properly in relation to each other", () => { + const expectedProgressCircleRadius = baseRadius - thickness / 2; + const expectedEmptyCircleRadius = expectedProgressCircleRadius - emptyThickness / 2; + compareRadiusValues(wrapper, expectedProgressCircleRadius, expectedEmptyCircleRadius); + }); + }); + describe("#lineMode.mode.bottom", () => { + const offset = 10; // must be ignored in this mode + let thickness = 40; + let emptyThickness = 10; + const wrapper = factory({ + thickness, + emptyThickness, + lineMode: `bottom ${offset}`, + }); - describe("radius of the circles does not exceed the size and aligns properly in relation to each other", () => { - it("in case #thickness * 2 > #emptyThickness", () => { - const expectedProgressCircleRadius = baseRadius - thickness / 2; - const expectedEmptyCircleRadius = expectedProgressCircleRadius + emptyThickness / 2; - compareRadiusValues(wrapper, expectedProgressCircleRadius, expectedEmptyCircleRadius); - }); - it("in case #thickness * 2 <= #emptyThickness", async () => { - thickness = 10; - emptyThickness = 20; + describe("radius of the circles does not exceed the size and aligns properly in relation to each other", () => { + it("in case #thickness * 2 > #emptyThickness", () => { + const expectedProgressCircleRadius = baseRadius - thickness / 2; + const expectedEmptyCircleRadius = expectedProgressCircleRadius + emptyThickness / 2; + compareRadiusValues(wrapper, expectedProgressCircleRadius, expectedEmptyCircleRadius); + }); + it("in case #thickness * 2 <= #emptyThickness", async () => { + thickness = 10; + emptyThickness = 20; - wrapper.setProps({ thickness, emptyThickness }); - await Vue.nextTick(); + wrapper.setProps({ thickness, emptyThickness }); + await Vue.nextTick(); - let expectedEmptyCircleRadius = baseRadius - emptyThickness / 2; - let expectedProgressCircleRadius = expectedEmptyCircleRadius - emptyThickness / 2; - compareRadiusValues(wrapper, expectedProgressCircleRadius, expectedEmptyCircleRadius); + let expectedEmptyCircleRadius = baseRadius - emptyThickness / 2; + let expectedProgressCircleRadius = expectedEmptyCircleRadius - emptyThickness / 2; + compareRadiusValues(wrapper, expectedProgressCircleRadius, expectedEmptyCircleRadius); - thickness = emptyThickness = 20; + thickness = emptyThickness = 20; - wrapper.setProps({ thickness, emptyThickness }); - await Vue.nextTick(); + wrapper.setProps({ thickness, emptyThickness }); + await Vue.nextTick(); - expectedEmptyCircleRadius = baseRadius - emptyThickness / 2; - expectedProgressCircleRadius = expectedEmptyCircleRadius - emptyThickness / 2; - compareRadiusValues(wrapper, expectedProgressCircleRadius, expectedEmptyCircleRadius); - }); + expectedEmptyCircleRadius = baseRadius - emptyThickness / 2; + expectedProgressCircleRadius = expectedEmptyCircleRadius - emptyThickness / 2; + compareRadiusValues(wrapper, expectedProgressCircleRadius, expectedEmptyCircleRadius); }); }); }); }); -}; +}); diff --git a/tests/unit/circle/circle-thickness.spec.js b/tests/unit/circle/circle-thickness.spec.js index 2c7e313..6886d3d 100644 --- a/tests/unit/circle/circle-thickness.spec.js +++ b/tests/unit/circle/circle-thickness.spec.js @@ -14,47 +14,45 @@ const factory = (propsData) => { }); }; -export default () => { - describe("#thickness", () => { - it("renders the progress circle line stroke thickness correctly", () => { - const thickness = 4; - - const wrapper = factory({ thickness }); - const circleProgressWrapper = wrapper.find("circle.ep-circle--progress"); - - expect(circleProgressWrapper.element.getAttribute("stroke-width")).to.equal(`${thickness}`); - }); - it("renders and calculates the progress circle line stroke relative thickness correctly", () => { - const size = 200; - const thickness = "5%"; - const relativeThickness = (parseInt(thickness, 10) * size) / 100; - - const wrapper = factory({ thickness }); - const circleProgressWrapper = wrapper.find("circle.ep-circle--progress"); - - expect(wrapper.vm.computedThickness).to.equal(relativeThickness); - expect(circleProgressWrapper.element.getAttribute("stroke-width")).to.equal(`${relativeThickness}`); - }); +describe("#thickness", () => { + it("renders the progress circle line stroke thickness correctly", () => { + const thickness = 4; + + const wrapper = factory({ thickness }); + const circleProgressWrapper = wrapper.find("circle.ep-circle--progress"); + + expect(circleProgressWrapper.element.getAttribute("stroke-width")).to.equal(`${thickness}`); }); - describe("#emptyTthickness", () => { - it("renders the empty circle line stroke thickness correctly", () => { - const emptyThickness = 4; - - const wrapper = factory({ emptyThickness }); - const circleEmptyWrapper = wrapper.find("circle.ep-circle--empty"); - - expect(circleEmptyWrapper.element.getAttribute("stroke-width")).to.equal(`${emptyThickness}`); - }); - it("renders and calculates the empty circle line stroke relative thickness correctly", () => { - const size = 200; - const emptyThickness = "5%"; - const relativeThickness = (parseInt(emptyThickness, 10) * size) / 100; - - const wrapper = factory({ emptyThickness }); - const circleEmptyWrapper = wrapper.find("circle.ep-circle--empty"); - - expect(wrapper.vm.computedEmptyThickness).to.equal(relativeThickness); - expect(circleEmptyWrapper.element.getAttribute("stroke-width")).to.equal(`${relativeThickness}`); - }); + it("renders and calculates the progress circle line stroke relative thickness correctly", () => { + const size = 200; + const thickness = "5%"; + const relativeThickness = (parseInt(thickness, 10) * size) / 100; + + const wrapper = factory({ thickness }); + const circleProgressWrapper = wrapper.find("circle.ep-circle--progress"); + + expect(wrapper.vm.computedThickness).to.equal(relativeThickness); + expect(circleProgressWrapper.element.getAttribute("stroke-width")).to.equal(`${relativeThickness}`); }); -}; +}); +describe("#emptyTthickness", () => { + it("renders the empty circle line stroke thickness correctly", () => { + const emptyThickness = 4; + + const wrapper = factory({ emptyThickness }); + const circleEmptyWrapper = wrapper.find("circle.ep-circle--empty"); + + expect(circleEmptyWrapper.element.getAttribute("stroke-width")).to.equal(`${emptyThickness}`); + }); + it("renders and calculates the empty circle line stroke relative thickness correctly", () => { + const size = 200; + const emptyThickness = "5%"; + const relativeThickness = (parseInt(emptyThickness, 10) * size) / 100; + + const wrapper = factory({ emptyThickness }); + const circleEmptyWrapper = wrapper.find("circle.ep-circle--empty"); + + expect(wrapper.vm.computedEmptyThickness).to.equal(relativeThickness); + expect(circleEmptyWrapper.element.getAttribute("stroke-width")).to.equal(`${relativeThickness}`); + }); +}); diff --git a/tests/unit/circle/circle.spec.js b/tests/unit/circle/circle.spec.js index 1621eef..4d6b201 100644 --- a/tests/unit/circle/circle.spec.js +++ b/tests/unit/circle/circle.spec.js @@ -4,11 +4,13 @@ import Vue from "vue"; import Circle from "../../../src/components/Circle/Circle.vue"; import HalfCircle from "../../../src/components/Circle/HalfCircle.vue"; import VueEllipseProgress from "../../../src/components/VueEllipseProgress.vue"; +import { dotParser } from "../../../src/components/optionsParser"; -import lineTest from "./circle-line.spec"; -import thicknessTest from "./circle-thickness.spec"; -import animationTest from "./circle-animation.spec"; -import colorsTest from "./circle-colors.spec"; +import "./circle-line.spec"; +import "./circle-thickness.spec"; +import "./circle-animation.spec"; +import "./circle-colors.spec"; +import "./cirlce-dot.spec"; const factory = (propsData, container = Circle) => { return mount(container, { @@ -232,9 +234,10 @@ describe("[ CircleProgress.vue | HalfCircleProgress.vue ]", () => { }); }); describe("#data", () => { - const size = 400; + const size = 600; const globalThickness = 5; const globalGap = 5; + const globalDot = "2%"; const data = []; // generate random test data @@ -247,29 +250,44 @@ describe("[ CircleProgress.vue | HalfCircleProgress.vue ]", () => { } // some special cases data.push({ progress: 50, thickness: 5 }); + data.push({ progress: 50, thickness: "2%", dot: 0 }); + data.push({ progress: 50, thickness: "4%", gap: 3 }); + data.push({ progress: 50, thickness: "4%", gap: 3, dot: 5 }); + data.push({ progress: 50, thickness: "0%", gap: 3, dot: "5% red" }); + data.push({ progress: 50, thickness: 6, gap: 5, dot: "5 red" }); data.push({ progress: 50, gap: 5 }); data.push({ progress: 50, gap: 0 }); data.push({ progress: 50 }); - const wrapper = factory({ data, gap: globalGap, thickness: globalThickness, size }, VueEllipseProgress); + const wrapper = factory( + { data, gap: globalGap, thickness: globalThickness, size, dot: globalDot }, + VueEllipseProgress + ); const circleWrappers = wrapper.findAll(Circle); + const calculateThickness = (t) => (t.toString().includes("%") ? (parseFloat(t) * size) / 100 : t); + for (let i = 0; i < data.length; i++) { const circleData = data[i]; it(`calculates the radius of circle #${i} correctly - #thickness ${circleData.thickness} | #gap ${circleData.gap} `, () => { + #thickness ${circleData.thickness} | #gap ${circleData.gap} | #dot ${circleData.dot} `, () => { const circleGap = circleData.gap !== undefined ? circleData.gap : globalGap; - const circleThickness = circleData.thickness !== undefined ? circleData.thickness : globalThickness; + const circleThickness = calculateThickness( + circleData.thickness !== undefined ? circleData.thickness : globalThickness + ); + const circleDot = calculateThickness(dotParser(circleData.dot !== undefined ? circleData.dot : globalDot).size); let radius; - const baseRadius = size / 2 - circleThickness / 2; + const baseRadius = size / 2 - Math.max(circleThickness, circleDot) / 2; if (i > 0) { const previousCirclesData = data.filter((props, index) => index < i); const previousCirclesThickness = previousCirclesData - .map(({ gap, thickness }, n) => { + .map(({ gap, thickness, dot }, n) => { const g = gap !== undefined ? gap : globalGap; - const t = thickness !== undefined ? thickness : globalThickness; - return n > 0 ? g + t : t; + const t = calculateThickness(thickness !== undefined ? thickness : globalThickness); + const d = calculateThickness(dotParser(dot !== undefined ? dot : globalDot).size); + const thicknessWithDot = Math.max(t, d); + return n > 0 ? g + thicknessWithDot : thicknessWithDot; }) .reduce((acc, current) => acc + current); @@ -284,9 +302,4 @@ describe("[ CircleProgress.vue | HalfCircleProgress.vue ]", () => { }); } }); - - thicknessTest(); - lineTest(); - animationTest(); - colorsTest(); }); diff --git a/tests/unit/circle/cirlce-dot.spec.js b/tests/unit/circle/cirlce-dot.spec.js new file mode 100644 index 0000000..818669e --- /dev/null +++ b/tests/unit/circle/cirlce-dot.spec.js @@ -0,0 +1,143 @@ +import { expect } from "chai"; +import { mount } from "@vue/test-utils"; +import Vue from "vue"; +import CircleContainer from "../../../src/components/Circle/CircleContainer.vue"; +import VueEllipseProgress from "../../../src/components/VueEllipseProgress.vue"; +import Circle from "../../../src/components/Circle/Circle.vue"; +import CircleDot from "../../../src/components/Circle/CircleDot.vue"; +import { dotParser } from "../../../src/components/optionsParser"; + +const factory = (propsData, container = Circle) => { + return mount(container, { + propsData: { + index: 0, + id: 123, + multiple: false, + ...propsData, + }, + }); +}; + +describe("#dot", () => { + const progress = 50; + const thickness = 5; + const size = 500; + const globalDot = "5%"; + + const calculateThickness = (t) => (t.toString().includes("%") ? (parseFloat(t) * size) / 100 : t); + + it(`parses property correctly`, () => { + const wrapper = factory({ progress, size }); + let dot = 0; + expect(wrapper.vm.parsedDot.size).to.equal("0"); + expect(wrapper.vm.parsedDot.color).to.equal("white"); + + dot = "5% red"; + wrapper.setProps({ dot }); + expect(wrapper.vm.parsedDot.size).to.equal("5%"); + expect(wrapper.vm.parsedDot.color).to.equal("red"); + + dot = { size: 10, backgroundColor: "green" }; + wrapper.setProps({ dot }); + expect(wrapper.vm.parsedDot.size).to.equal(10); + expect(wrapper.vm.parsedDot.color).to.equal("white"); + expect(wrapper.vm.parsedDot.backgroundColor).to.equal("green"); + }); + + it(`converts the size percent value to pixel correctly`, () => { + const dot = "5%"; + const wrapper = factory({ progress, size, dot }); + const dotPixelSize = calculateThickness(dot); + expect(wrapper.vm.dotSize).to.equal(dotPixelSize); + }); + + it("applies default value correctly", () => { + const wrapper = factory({ progress }, VueEllipseProgress); + const circleWrapper = wrapper.find(Circle); + const circleContainerWrapper = wrapper.find(CircleContainer); + + expect(wrapper.props("dot")).to.equal(0); + expect(circleContainerWrapper.props("dot")).to.equal(0); + expect(circleWrapper.vm.parsedDot.size).to.equal("0"); + expect(circleWrapper.vm.parsedDot.color).to.equal("white"); + expect(circleWrapper.vm.dotSize).to.equal(0); + }); + + it(`calculates and applies correct rotation of the dot container depending on progress`, (done) => { + const wrapper = factory({ progress, dot: 5, animation: "default 0 0" }, CircleContainer); + const circleDotWrapper = wrapper.find(CircleDot); + const rotationStart = wrapper.props("angle") + 90; + const rotation = rotationStart + (progress * 360) / 100; + setTimeout(() => { + expect(circleDotWrapper.element.style.transform).to.equal(`rotate(${rotation}deg)`); + done(); + }, 0); + }); + + it(`applies correct initial rotation of the dot container`, async () => { + const wrapper = factory({ progress, dot: 5, animation: "default 0 1000" }, CircleContainer); + const circleDotWrapper = wrapper.find(CircleDot); + const angle = wrapper.props("angle"); + const rotationStart = angle + 90; + expect(circleDotWrapper.element.style.transform).to.equal(`rotate(${rotationStart}deg)`); + + wrapper.setProps({ half: true }); + const halfRotationStart = angle - 90; + await Vue.nextTick(); + expect(circleDotWrapper.element.style.transform).to.equal(`rotate(${halfRotationStart}deg)`); + }); + + const data = [ + { progress, thickness, dot: 5 }, + { progress, thickness, dot: "5" }, + { progress, thickness, dot: "5%" }, + { progress, thickness, dot: "5% red" }, + { progress, thickness, dot: "5 blue" }, + { progress, thickness, dot: { size: 5 } }, + { progress, thickness, dot: { size: "5%" } }, + { progress, thickness, dot: { size: 5, backgroundColor: "yellow" } }, + { progress, thickness, dot: { size: "3%", background: "green", borderRadius: "2px" } }, + ]; + + for (let i = 0; i < data.length; i++) { + const circleData = data[i]; + const wrapper = factory({ size, dot: globalDot, ...circleData }, CircleContainer); + const circleDotSpanWrapper = wrapper.find("span.ep-circle--progress__dot"); + const circleDotWrapper = wrapper.find(CircleDot); + const circleWrapper = wrapper.find(Circle); + const parsedDot = dotParser(circleData.dot !== undefined ? circleData.dot : globalDot); + const parsedDotSize = parseFloat(calculateThickness(parsedDot.size)); + const parsedDotColor = parsedDot.backgroundColor || parsedDot.background || parsedDot.color; + + it(`renders dot component | #dot = ${circleData.dot}`, () => { + expect(wrapper.contains(CircleDot)).to.be.true; + }); + + it(`applies the size of the dot correctly | #dot = ${circleData.dot}`, () => { + expect(circleDotSpanWrapper.element.style.width).to.equal(`${parsedDotSize}px`); + }); + + it(`applies the color of the dot correctly | #dot = ${circleData.dot}`, () => { + const { background } = circleDotSpanWrapper.element.style; + if (background) { + expect(circleDotSpanWrapper.element.style.background).to.equal(`${parsedDotColor}`); + } else { + expect(circleDotSpanWrapper.element.style.backgroundColor).to.equal(`${parsedDotColor}`); + } + }); + + it(`calculates and applies the position of the dot container correctly | #dot = ${circleData.dot}`, () => { + const circleRadius = circleWrapper.vm.radius; + const xAndYPosition = (size - circleRadius * 2) / 2 - parsedDotSize / 2; + expect(circleDotWrapper.element.getAttribute("y")).to.equal(`${xAndYPosition}`); + expect(circleDotWrapper.element.getAttribute("x")).to.equal(`${xAndYPosition}`); + }); + + it(`calculates and applies the size of the dot container correctly | #dot = ${circleData.dot}`, () => { + const circleRadius = circleWrapper.vm.radius; + const containerSize = circleRadius * 2 + parsedDotSize; + expect(circleDotWrapper.element.getAttribute("width")).to.equal(`${containerSize}`); + expect(circleDotWrapper.element.getAttribute("height")).to.equal(`${containerSize}`); + }); + } +});
This is caption slot