Skip to content

Commit

Permalink
add more tests (#9688)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jan authored and AlexAndBear committed Dec 13, 2023
1 parent 09b524e commit dfe1841
Show file tree
Hide file tree
Showing 8 changed files with 172 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ Additionally we added a show/hide toggle button to password input field
https://github.com/owncloud/web/pull/9682
https://github.com/owncloud/web/pull/9634
https://github.com/owncloud/web/pull/9686
https://github.com/owncloud/web/pull/9688
https://github.com/owncloud/web/issues/9638
https://github.com/owncloud/web/issues/9657
2 changes: 1 addition & 1 deletion packages/design-system/src/components/OcModal/OcModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ import OcIcon from '../OcIcon/OcIcon.vue'
import OcTextInput from '../OcTextInput/OcTextInput.vue'
import { FocusTrap } from 'focus-trap-vue'
import { FocusTargetOrFalse, FocusTargetValueOrFalse } from 'focus-trap'
import { PasswordPolicy } from '../_OcTextInputPassword/_OcTextInputPassword.vue'
import { PasswordPolicy } from '../../helpers'
/**
* Modals are generally used to force the user to focus on confirming or completing a single action.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { shallowMount, mount, defaultPlugins } from 'web-test-helpers'
import OcTextInput from './OcTextInput.vue'
import { PasswordPolicy } from '../../helpers'
import { mock } from 'jest-mock-extended'

const defaultProps = {
label: 'label'
Expand All @@ -22,7 +24,24 @@ describe('OcTextInput', () => {
})
}

function getMountedWrapper(options = {}) {
function getMountedWrapper(options = {} as any, passwordPolicy = { active: false, pass: false }) {
const passwordPolicyMock = mock<PasswordPolicy>()
passwordPolicyMock.missing.mockReturnValueOnce({
rules: [
{
code: 'minLength',
message: 'At least %{param1} characters',
format: ['8'],
verified: passwordPolicy.pass
}
]
})
passwordPolicyMock.check.mockReturnValueOnce(passwordPolicy.pass)

if (passwordPolicy.active) {
options.props = { ...(options.props || {}), passwordPolicy: passwordPolicyMock }
}

return mount(OcTextInput, {
...options,
global: {
Expand Down Expand Up @@ -65,7 +84,6 @@ describe('OcTextInput', () => {
it('should not exist if type is not "password" or no value entered', () => {
const wrapper = getMountedWrapper()
expect(wrapper.find(selectors.copyPasswordBtn).exists()).toBeFalsy()

const wrapper2 = getMountedWrapper({ props: { type: 'password' } })
expect(wrapper2.find(selectors.copyPasswordBtn).exists()).toBeFalsy()
})
Expand Down Expand Up @@ -103,6 +121,48 @@ describe('OcTextInput', () => {
expect(wrapper.find(selectors.inputField).attributes().type).toBe('password')
})
})
describe('password policy', () => {
it('should emit "passwordChallengeFailed" if password does not match criteria', async () => {
const wrapper = getMountedWrapper(
{
props: { type: 'password' }
},
{ active: true, pass: false }
)
await wrapper.find(selectors.inputField).setValue('pass')
expect(wrapper.emitted('passwordChallengeCompleted')).toBeFalsy()
})
it('should emit "passwordChallengeCompleted" if password matches criteria', async () => {
const wrapper = getMountedWrapper(
{
props: { type: 'password' }
},
{ active: true, pass: true }
)
await wrapper.find(selectors.inputField).setValue('password123')
expect(wrapper.emitted('passwordChallengeCompleted')).toBeTruthy()
})
it('displays error state if password does not match criteria', async () => {
const wrapper = getMountedWrapper(
{
props: { type: 'password' }
},
{ active: true, pass: false }
)
await wrapper.find(selectors.inputField).setValue('pass')
expect(wrapper.html()).toMatchSnapshot()
})
it('displays success state if password matches criteria', async () => {
const wrapper = getMountedWrapper(
{
props: { type: 'password' }
},
{ active: true, pass: true }
)
await wrapper.find(selectors.inputField).setValue('password123')
expect(wrapper.html()).toMatchSnapshot()
})
})
})

describe('when a description message is provided', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,8 @@ import { defineComponent, HTMLAttributes, PropType } from 'vue'
import uniqueId from '../../utils/uniqueId'
import OcButton from '../OcButton/OcButton.vue'
import OcIcon from '../OcIcon/OcIcon.vue'
import OcTextInputPassword, {
PasswordPolicy
} from '../_OcTextInputPassword/_OcTextInputPassword.vue'
import OcTextInputPassword from '../_OcTextInputPassword/_OcTextInputPassword.vue'
import { PasswordPolicy } from '../../helpers'
/**
* Form Inputs are used to allow users to provide text input when the expected
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`OcTextInput password input field password policy displays error state if password does not match criteria 1`] = `
<div class="">
<label class="oc-label" for="oc-textinput-17"></label>
<div class="oc-position-relative">
<!--v-if-->
<div class="oc-text-input-password-wrapper">
<input aria-invalid="false" class="oc-text-input oc-input oc-rounded" id="oc-textinput-17" type="password">
<button class="oc-button oc-rounded oc-button-s oc-button-justify-content-center oc-button-gap-m oc-button-passive oc-button-passive-raw oc-text-input-copy-password-button oc-px-s oc-background-default" type="button">
<!--v-if-->
<!-- @slot Content of the button -->
<span class="oc-icon oc-icon-s oc-icon-passive">
<!---->
</span>
</button>
<button class="oc-button oc-rounded oc-button-s oc-button-justify-content-center oc-button-gap-m oc-button-passive oc-button-passive-raw oc-text-input-show-password-toggle oc-px-s oc-background-default" type="button">
<!--v-if-->
<!-- @slot Content of the button -->
<span class="oc-icon oc-icon-s oc-icon-passive">
<!---->
</span>
</button>
</div>
<portal to="app.design-system.password-policy">
<div class="oc-text-small oc-flex oc-flex-column">
<span>Please enter a password that meets the following criteria:</span>
<div class="oc-flex oc-flex-middle">
<span class="oc-icon oc-icon-s oc-icon-danger">
<!---->
</span>
<span class="oc-text-input-danger">At least 8 characters</span>
</div>
</div>
</portal>
<!--v-if-->
</div>
<!--v-if-->
<portal-target name="app.design-system.password-policy"></portal-target>
</div>
`;
exports[`OcTextInput password input field password policy displays success state if password matches criteria 1`] = `
<div class="">
<label class="oc-label" for="oc-textinput-18"></label>
<div class="oc-position-relative">
<!--v-if-->
<div class="oc-text-input-password-wrapper">
<input aria-invalid="false" class="oc-text-input oc-input oc-rounded" id="oc-textinput-18" type="password">
<button class="oc-button oc-rounded oc-button-s oc-button-justify-content-center oc-button-gap-m oc-button-passive oc-button-passive-raw oc-text-input-copy-password-button oc-px-s oc-background-default" type="button">
<!--v-if-->
<!-- @slot Content of the button -->
<span class="oc-icon oc-icon-s oc-icon-passive">
<!---->
</span>
</button>
<button class="oc-button oc-rounded oc-button-s oc-button-justify-content-center oc-button-gap-m oc-button-passive oc-button-passive-raw oc-text-input-show-password-toggle oc-px-s oc-background-default" type="button">
<!--v-if-->
<!-- @slot Content of the button -->
<span class="oc-icon oc-icon-s oc-icon-passive">
<!---->
</span>
</button>
</div>
<portal to="app.design-system.password-policy">
<div class="oc-text-small oc-flex oc-flex-column">
<span>Please enter a password that meets the following criteria:</span>
<div class="oc-flex oc-flex-middle">
<span class="oc-icon oc-icon-s oc-icon-success">
<!---->
</span>
<span class="oc-text-input-success">At least 8 characters</span>
</div>
</div>
</portal>
<!--v-if-->
</div>
<!--v-if-->
<portal-target name="app.design-system.password-policy"></portal-target>
</div>
`;
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,7 @@ import { computed, defineComponent, PropType, ref, unref, watch } from 'vue'
import OcIcon from '../OcIcon/OcIcon.vue'
import OcButton from '../OcButton/OcButton.vue'
import { useGettext } from 'vue3-gettext'
export interface PasswordPolicy {
rules: unknown[]
check(password: string): boolean
missing(password: string): {
rules: {
code: string
message: string
format: (number | string)[]
verified: boolean
}[]
}
}
import { PasswordPolicy } from '../../helpers'
export default defineComponent({
name: 'OCTextInputPassword',
components: { OcButton, OcIcon },
Expand Down
15 changes: 15 additions & 0 deletions packages/design-system/src/helpers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,18 @@ export interface ContextualHelper {
isEnabled: boolean
data: ContextualHelperData
}

export interface PasswordPolicy {
rules: unknown[]

check(password: string): boolean

missing(password: string): {
rules: {
code: string
message: string
format: (number | string)[]
verified: boolean
}[]
}
}
15 changes: 9 additions & 6 deletions packages/web-pkg/src/services/passwordPolicy/passwordPolicy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ export class PasswordPolicyService {
this.buildPolicy()
}

private useDefaultRules(): boolean {
return (
Object.keys(this.capability).length === 0 ||
(Object.keys(this.capability).length === 1 &&
Object.keys(this.capability)[0] === 'max_characters')
)
}

private buildPolicy() {
const ruleset = {
atLeastCharacters: new AtLeastCharactersRule({ ...this.language }),
Expand All @@ -36,12 +44,7 @@ export class PasswordPolicyService {
}
const rules = {} as any

// add default rule
if (
Object.keys(this.capability).length === 0 ||
(Object.keys(this.capability).length === 1 &&
Object.keys(this.capability)[0] === 'max_characters')
) {
if (this.useDefaultRules()) {
rules.mustNotBeEmpty = {}
}

Expand Down

0 comments on commit dfe1841

Please sign in to comment.