Skip to content

Michal Wawryszczuk challenge #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
"eslint": "^8.22.0",
"eslint-plugin-vue": "^9.3.0",
"jsdom": "^20.0.3",
"node-sass": "^9.0.0",
"prettier": "^2.7.1",
"sass": "^1.62.1",
"sass-loader": "^13.3.1",
"vite": "^3.2.4",
"vitest": "^0.25.3"
}
Expand Down
88 changes: 76 additions & 12 deletions src/App.vue
Original file line number Diff line number Diff line change
@@ -1,26 +1,90 @@
<script setup>
import { RULE } from "@/domain/password/rules";
import { useStrongPasswordStore } from "@/stores/strong-password";
import StrengthIndicator from "./components/strengthIndicator.vue";

const rules = Object.values(RULE);
const strongPasswordStore = useStrongPasswordStore();
</script>

<template>
<div>
<input data-test="password-field" />
<div class="password-challenge">
<input
class="password-challenge__input"
v-model="strongPasswordStore.password"
@input="strongPasswordStore.validateInput"
placeholder="Type your password"
type="password"
/>

<ul>
<ul class="password-challenge__hints">
<li
v-for="rule in rules"
:key="rule"
:data-test-rule-indicator="rule"
class="password-hint__rule password-hint__rule--fail"
v-for="rule in strongPasswordStore.rules"
:key="rule.name"
class="password-challenge__hints-rule"
:class="{'password-challenge__hints-rule--passed': rule.isCorrect}"
>
HINT

<transition name="reduce" class="password-challenge__hints-rule-icon" mode="out-in">
<i v-if="!rule.isCorrect" class="icon-notPassedIcon"></i>
<i v-else class="icon-passedIcon"></i>
</transition>

<div class="password-challenge__hints-rule-label">{{ rule.label }}</div>
</li>
</ul>

<span data-test="validation-summary">Strong or Weak?</span>
<StrengthIndicator/>
</div>
</template>

<style scoped></style>
<style scoped lang="scss">
.password-challenge {
display: flex;
flex-direction: column;
row-gap: 20px;
margin: 0 auto;
padding: 30px;
max-width: 600px;
font-family: Trebuchet MS, Tahoma, Arial, sans-serif;
}

.password-challenge__input {
padding: 10px;
border-radius: 10px;
border: 1px solid $color-lightgray;
font-family: Trebuchet MS, Tahoma, Arial, sans-serif;
font-size: 18px;

&:focus-visible {
outline: 1px solid $color-lightgray;
}

&::placeholder {
color: $color-lightgray;
}
}

.password-challenge__hints {
display: flex;
flex-direction: column;
row-gap: 10px;
margin: 0;
padding: 0;
list-style-type: none;
}

.password-challenge__hints-rule {
display: flex;
gap: 10px;
align-items: center;
color: $color-red;
transition: color 0.3s ease-in-out;

&--passed {
color: $color-green;
}
}

.password-challenge__hints-rule-icon {
display: flex;
}
</style>
25 changes: 25 additions & 0 deletions src/assets/base.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//VARIABLES
$color-red: #c70000;
$color-green: #008000;
$color-yellow: #ffc300;
$color-lightgray: #d3d3d3;
$color-background: #f4f4f4;
$color-indicator: #cbcbcb;


//ANIMATIONS
.reduce-enter-from,
.reduce-leave-to {
transform: scale(0);
}

.reduce-enter-to,
.reduce-leave-from {
transform: scale(1);
}

.reduce-move,
.reduce-enter-active,
.reduce-leave-active {
transition: transform 0.3s ease-in-out;
}
Binary file added src/assets/icomoon/icomoon.ttf
Binary file not shown.
Binary file added src/assets/icomoon/icomoon.woff
Binary file not shown.
1 change: 1 addition & 0 deletions src/assets/icomoon/selection.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"IcoMoonType":"selection","icons":[{"icon":{"paths":["M512.006-0.008c-0.003-0-0.006-0-0.010-0-282.771 0-512.003 229.23-512.005 512.001l-0 0c-0 0.003-0 0.006-0 0.010 0 282.773 229.232 512.005 512.005 512.005 0.003 0 0.006-0 0.009-0l0-0c282.771-0.002 512.002-229.234 512.002-512.005 0-0.003-0-0.007-0-0.010l0 0.001c-0.002-282.77-229.232-512-512.002-512.002l0.001 0zM714.755 212.512l90.768 88.145-204.034 210.064 210.013 204.034-88.145 90.768-210.064-204.034-204.034 210.013-90.781-88.196 204.047-210.013-210.026-204.034 88.158-90.768 210.051 204.034z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["notPassedIcon"]},"attrs":[{}],"properties":{"order":2,"id":1,"name":"notPassedIcon","prevSize":32,"code":59648},"setIdx":0,"setId":2,"iconIdx":0},{"icon":{"paths":["M512-0.001c-282.77 0.002-512 229.232-512.002 512.002l0-0.001c0.002 282.77 229.232 512 512.002 512.002l-0-0c282.77-0.002 512-229.232 512.002-512.002l-0 0.001c-0.002-282.77-229.232-512-512.002-512.002l0 0zM738.572 194.673l99.947 77.551-432.316 557.103-220.709-319.435 104.087-71.92 122.549 177.357z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["passedIcon"]},"attrs":[{}],"properties":{"order":3,"id":0,"name":"passedIcon","prevSize":32,"code":59649},"setIdx":0,"setId":2,"iconIdx":1}],"height":1024,"metadata":{"name":"icomoon"},"preferences":{"showGlyphs":true,"showQuickUse":true,"showQuickUse2":true,"showSVGs":true,"fontPref":{"prefix":"icon-","metadata":{"fontFamily":"icomoon","majorVersion":1,"minorVersion":0},"metrics":{"emSize":1024,"baseline":6.25,"whitespace":50},"embed":false,"cssVars":true,"cssVarsFormat":"scss","showSelector":false,"showMetrics":false,"showMetadata":false,"showVersion":false},"imagePref":{"prefix":"icon-","png":true,"useClassSelector":true,"color":0,"bgColor":16777215,"classSelector":".icon"},"historySize":50,"showCodes":true,"gridSize":16}}
6 changes: 6 additions & 0 deletions src/assets/iconVariables.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
$icomoon-font-family: "icomoon";
$icomoon-font-path: "src/assets/icomoon";

$icon-notPassedIcon: "\e900";
$icon-passedIcon: "\e901";

40 changes: 40 additions & 0 deletions src/assets/icons.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
@import './iconVariables.scss';

@font-face {
font-family: '#{$icomoon-font-family}';
src: url('#{$icomoon-font-path}/#{$icomoon-font-family}.eot?pfwaw0');
src: url('#{$icomoon-font-path}/#{$icomoon-font-family}.eot?pfwaw0#iefix') format('embedded-opentype'),
url('#{$icomoon-font-path}/#{$icomoon-font-family}.ttf?pfwaw0') format('truetype'),
url('#{$icomoon-font-path}/#{$icomoon-font-family}.woff?pfwaw0') format('woff'),
url('#{$icomoon-font-path}/#{$icomoon-font-family}.svg?pfwaw0##{$icomoon-font-family}') format('svg');
font-weight: normal;
font-style: normal;
font-display: block;
}

[class^="icon-"], [class*=" icon-"] {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: '#{$icomoon-font-family}' !important;
speak: never;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;

/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

.icon-notPassedIcon {
&:before {
content: $icon-notPassedIcon;
}
}
.icon-passedIcon {
&:before {
content: $icon-passedIcon;
}
}

Empty file removed src/assets/main.css
Empty file.
5 changes: 5 additions & 0 deletions src/assets/main.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@import './icons';

body {
background-color: $color-background;
}
66 changes: 66 additions & 0 deletions src/components/StrengthIndicator.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<template>
<div class="cmp-strength-indicator">
<div class="cmp-strength-indicator__label">Your password is <span>{{ message }}</span></div>
<div class="cmp-strength-indicator__wrapper">
<div class="cmp-strength-indicator__level" :class="indicatorClasses"></div>
<div class="cmp-strength-indicator__level" :class="{'cmp-strength-indicator__level--strong': strongPasswordStore.isPasswordStrong}"></div>
</div>
</div>
</template>

<script setup>
import { computed } from 'vue';
import { useStrongPasswordStore } from "@/stores/strong-password";

const strongPasswordStore = useStrongPasswordStore();

const message = computed(() => {
if (strongPasswordStore.isPasswordStrong) {
return 'strong';
} else if (strongPasswordStore.isPasswordWeak) {
return 'weak';
} else return 'uncorrect';
})

const indicatorClasses = computed(() => {
if (strongPasswordStore.isPasswordWeak) {
return 'cmp-strength-indicator__level--weak';
} else if (strongPasswordStore.isPasswordStrong) {
return 'cmp-strength-indicator__level--strong'
}
})
</script>

<style lang="scss" scoped>
.cmp-strength-indicator {
margin-top: 20px;
}
.cmp-strength-indicator__label {
text-align: right;

span {
font-weight: 700;
}
}
.cmp-strength-indicator__wrapper {
display: flex;
gap: 5px;
margin-top: 10px;
}

.cmp-strength-indicator__level {
height: 3px;
width: 100%;
background-color: $color-indicator;
border-radius: 3px;
transition: background-color 0.3s ease-in-out;

&--weak {
background-color: $color-yellow;
}

&--strong {
background-color: $color-green;
}
}
</style>
29 changes: 29 additions & 0 deletions src/customTests/Password.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { describe, it, beforeEach, expect } from "vitest";
import { createPinia, setActivePinia } from "pinia/dist/pinia";
import { useStrongPasswordStore } from "@/stores/strong-password";

describe('Password', () => {
beforeEach(() => {
setActivePinia(createPinia())
});

it('At least one letter', () => {
const strongPasswordStore = useStrongPasswordStore();
const oneLetter = strongPasswordStore.rules.find(rule => rule.name === 'OneLetter');

const expectTruthy = ['a', 'B', 'abc', '1a2s', 'AAbb', 'Mot%!'];
const expectFalsy = ['', 1, '%', '1@3'];

expectTruthy.forEach(passwordCanidate => {
strongPasswordStore.password = passwordCanidate;
strongPasswordStore.validateInput();
expect(oneLetter.isCorrect).toBeTruthy();
})

expectFalsy.forEach(passwordCanidate => {
strongPasswordStore.password = passwordCanidate;
strongPasswordStore.validateInput();
expect(oneLetter.isCorrect).toBeFalsy();
})
})
})
2 changes: 1 addition & 1 deletion src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createApp } from "vue";
import { createPinia } from "pinia";
import App from "./App.vue";

import "./assets/main.css";
import "./assets/main.scss";

const app = createApp(App);

Expand Down
68 changes: 66 additions & 2 deletions src/stores/strong-password.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,69 @@
import { defineStore } from "pinia";

export const useStrongPasswordStore = defineStore("strong_password", () => {
return {};
export const useStrongPasswordStore = defineStore("strong_password", {
state: () => ({
password: '',
rules: [
{
name: 'OneLetter',
pattern: /[a-zA-z]/,
label: 'Has at least one letter',
isCorrect: false
},
{
name: 'UpperAndLower',
pattern: /(?=.*[a-z])(?=.*[A-Z])/,
label: 'Has at least one lower and one upper case letter',
isCorrect: false
},
{
name: 'OneNumber',
pattern: /\d/,
label: 'Has at least one number',
isCorrect: false
},
{
name: 'SpecialSymbol',
pattern: /[^\w]/,
label: 'Has at least one special character',
isCorrect: false
},
{
name: 'LongerThan4',
pattern: /.{5,}/,
label: 'Has length > 4',
isCorrect: false
},
{
name: 'LongerThan8',
pattern: /.{9,}/,
label: 'Has length > 8',
isCorrect: false
},
{
name: 'LongerThan12',
pattern: /.{13,}/,
label: 'Has length > 12',
isCorrect: false
},
]
}),

getters: {
countPassedRules: (state) => state.rules.filter(rule => rule.isCorrect).length,
isPasswordStrong() {
return this.countPassedRules >= 5;
},
isPasswordWeak() {
return !!this.countPassedRules && this.countPassedRules < 5;
}
},

actions: {
validateInput() {
this.rules.forEach(rule => {
rule.pattern.test(this.password) ? rule.isCorrect = true : rule.isCorrect = false;
});
}
}
});
8 changes: 8 additions & 0 deletions vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,12 @@ export default defineConfig({
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
css: {
preprocessorOptions: {
scss: {
additionalData: `
@import "./src/assets/base.scss";`
}
}
}
});