From c9b5ee2cf3b0515e06fbd2c5e3266b4bea7a13be Mon Sep 17 00:00:00 2001 From: Jan Date: Tue, 12 Sep 2023 14:28:46 +0200 Subject: [PATCH] add more tests (#9688) --- ...ancement-add-password-policy-compatibility | 1 + .../src/components/OcModal/OcModal.vue | 2 +- .../OcTextInput/OcTextInput.spec.ts | 64 ++++++++++++++- .../components/OcTextInput/OcTextInput.vue | 5 +- .../__snapshots__/OcTextInput.spec.ts.snap | 81 +++++++++++++++++++ .../_OcTextInputPassword.vue | 14 +--- packages/design-system/src/helpers/types.ts | 15 ++++ .../services/passwordPolicy/passwordPolicy.ts | 15 ++-- 8 files changed, 172 insertions(+), 25 deletions(-) create mode 100644 packages/design-system/src/components/OcTextInput/__snapshots__/OcTextInput.spec.ts.snap diff --git a/changelog/unreleased/enhancement-add-password-policy-compatibility b/changelog/unreleased/enhancement-add-password-policy-compatibility index fef59ded90d..015859dd791 100644 --- a/changelog/unreleased/enhancement-add-password-policy-compatibility +++ b/changelog/unreleased/enhancement-add-password-policy-compatibility @@ -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 diff --git a/packages/design-system/src/components/OcModal/OcModal.vue b/packages/design-system/src/components/OcModal/OcModal.vue index 257416d6960..514d2a5928d 100644 --- a/packages/design-system/src/components/OcModal/OcModal.vue +++ b/packages/design-system/src/components/OcModal/OcModal.vue @@ -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. diff --git a/packages/design-system/src/components/OcTextInput/OcTextInput.spec.ts b/packages/design-system/src/components/OcTextInput/OcTextInput.spec.ts index b14440bdc0d..c65c2b5b279 100644 --- a/packages/design-system/src/components/OcTextInput/OcTextInput.spec.ts +++ b/packages/design-system/src/components/OcTextInput/OcTextInput.spec.ts @@ -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' @@ -22,7 +24,24 @@ describe('OcTextInput', () => { }) } - function getMountedWrapper(options = {}) { + function getMountedWrapper(options = {} as any, passwordPolicy = { active: false, pass: false }) { + const passwordPolicyMock = mock() + 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: { @@ -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() }) @@ -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', () => { diff --git a/packages/design-system/src/components/OcTextInput/OcTextInput.vue b/packages/design-system/src/components/OcTextInput/OcTextInput.vue index 2cc98e05876..bbb67cce3ea 100644 --- a/packages/design-system/src/components/OcTextInput/OcTextInput.vue +++ b/packages/design-system/src/components/OcTextInput/OcTextInput.vue @@ -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 diff --git a/packages/design-system/src/components/OcTextInput/__snapshots__/OcTextInput.spec.ts.snap b/packages/design-system/src/components/OcTextInput/__snapshots__/OcTextInput.spec.ts.snap new file mode 100644 index 00000000000..6060716133e --- /dev/null +++ b/packages/design-system/src/components/OcTextInput/__snapshots__/OcTextInput.spec.ts.snap @@ -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`] = ` +
+ +
+ +
+ + + +
+ +
+ Please enter a password that meets the following criteria: +
+ + + + At least 8 characters +
+
+
+ +
+ + +
+`; + +exports[`OcTextInput password input field password policy displays success state if password matches criteria 1`] = ` +
+ +
+ +
+ + + +
+ +
+ Please enter a password that meets the following criteria: +
+ + + + At least 8 characters +
+
+
+ +
+ + +
+`; diff --git a/packages/design-system/src/components/_OcTextInputPassword/_OcTextInputPassword.vue b/packages/design-system/src/components/_OcTextInputPassword/_OcTextInputPassword.vue index 16298720369..ded10248855 100644 --- a/packages/design-system/src/components/_OcTextInputPassword/_OcTextInputPassword.vue +++ b/packages/design-system/src/components/_OcTextInputPassword/_OcTextInputPassword.vue @@ -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 }, diff --git a/packages/design-system/src/helpers/types.ts b/packages/design-system/src/helpers/types.ts index 5de150f6b84..b6ff198f132 100644 --- a/packages/design-system/src/helpers/types.ts +++ b/packages/design-system/src/helpers/types.ts @@ -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 + }[] + } +} diff --git a/packages/web-pkg/src/services/passwordPolicy/passwordPolicy.ts b/packages/web-pkg/src/services/passwordPolicy/passwordPolicy.ts index 25c584c1b98..5f7cd2c1817 100644 --- a/packages/web-pkg/src/services/passwordPolicy/passwordPolicy.ts +++ b/packages/web-pkg/src/services/passwordPolicy/passwordPolicy.ts @@ -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 }), @@ -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 = {} }