Skip to content
Open
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<template>

<KTextbox
:value="value"
type="email"
:label="label || $tr('emailLabel')"
:maxlength="maxlength"
:disabled="disabled"
:invalid="hasError"
:invalidText="errorText"
:showInvalidText="hasError"
v-bind="$attrs"
@input="handleInput"
@blur="$emit('blur')"
/>

</template>


<script>
export default {
name: 'StudioEmailField',
props: {
value: {
type: String,
default: '',
},
label: {
type: String,
default: null,
},
disabled: {
type: Boolean,
default: false,
},
errorMessages: {
type: Array,
default: () => [],
},
maxlength: {
type: [String, Number],
default: null,
},
},
computed: {
hasError() {
return this.errorMessages && this.errorMessages.length > 0;
},
errorText() {
return this.hasError ? this.errorMessages[0] : '';
},
},
methods: {
handleInput(value) {
this.$emit('input', value.trim());
},
},
$trs: {
emailLabel: 'Email address',
},
};
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<template>

<KTextbox
:value="value"
type="password"
:label="label || $tr('passwordLabel')"
:invalid="hasError"
:invalidText="errorText"
:showInvalidText="hasError"
@input="$emit('input', $event)"
@blur="$emit('blur')"
/>

</template>


<script>

export default {
name: 'StudioPasswordField',
props: {
value: {
type: String,
default: '',
},
label: {
type: String,
default: null,
},
errorMessages: {
type: Array,
default: () => [],
},
},
computed: {
hasError() {
return this.errorMessages && this.errorMessages.length > 0;
},
errorText() {
return this.hasError ? this.errorMessages[0] : '';
},
},
$trs: {
passwordLabel: 'Password',
},
};

</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import VueRouter from 'vue-router';
import { render, screen, fireEvent } from '@testing-library/vue';
import StudioEmailField from '../StudioEmailField.vue';

const renderComponent = (props = {}) =>
render(StudioEmailField, {
router: new VueRouter(),
props: {
value: '',
...props,
},
});

describe('StudioEmailField', () => {
describe('rendering', () => {
it('renders with the default "Email address" label', () => {
renderComponent();
expect(screen.getByLabelText(/email address/i)).toBeInTheDocument();
});

it('renders with a custom label when provided', () => {
renderComponent({ label: 'Work email' });
expect(screen.getByLabelText(/work email/i)).toBeInTheDocument();
});

it('is disabled when the disabled prop is true', () => {
renderComponent({ disabled: true });
expect(screen.getByLabelText(/email address/i)).toBeDisabled();
});
});

describe('input handling', () => {
it('emits trimmed value — strips leading and trailing whitespace', async () => {
const { emitted } = renderComponent();
const input = screen.getByLabelText(/email address/i);
await fireEvent.update(input, ' [email protected] ');
expect(emitted().input).toBeTruthy();
expect(emitted().input[0][0]).toBe('[email protected]');
});

it('emits blur event when the field loses focus', async () => {
const { emitted } = renderComponent();
const input = screen.getByLabelText(/email address/i);
await fireEvent.blur(input);
expect(emitted().blur).toBeTruthy();
});
});

describe('error display', () => {
it('shows the first error message when errorMessages is non-empty', () => {
renderComponent({ errorMessages: ['Please enter a valid email address'] });
expect(screen.getByText('Please enter a valid email address')).toBeVisible();
});

it('shows no error text when errorMessages is empty', () => {
renderComponent({ errorMessages: [] });
expect(screen.queryByText('Please enter a valid email address')).not.toBeInTheDocument();
});

it('shows only the first error when multiple messages are provided', () => {
renderComponent({ errorMessages: ['First error', 'Second error'] });
expect(screen.getByText('First error')).toBeVisible();
expect(screen.queryByText('Second error')).not.toBeInTheDocument();
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import VueRouter from 'vue-router';
import { render, screen, fireEvent } from '@testing-library/vue';
import StudioPasswordField from '../StudioPasswordField.vue';

const renderComponent = (props = {}) =>
render(StudioPasswordField, {
router: new VueRouter(),
props: {
value: '',
...props,
},
});

describe('StudioPasswordField', () => {
describe('rendering', () => {
it('renders with the default "Password" label', () => {
renderComponent();
expect(screen.getByLabelText(/^password$/i)).toBeInTheDocument();
});

it('renders with a custom label when provided', () => {
renderComponent({ label: 'Confirm password' });
expect(screen.getByLabelText(/confirm password/i)).toBeInTheDocument();
});
});

describe('input handling', () => {
it('emits raw value without trimming whitespace', async () => {
const { emitted } = renderComponent();
const input = screen.getByLabelText(/^password$/i);
await fireEvent.update(input, ' mypassword ');
expect(emitted().input).toBeTruthy();
expect(emitted().input[0][0]).toBe(' mypassword ');
});

it('emits blur event when the field loses focus', async () => {
const { emitted } = renderComponent();
const input = screen.getByLabelText(/^password$/i);
await fireEvent.blur(input);
expect(emitted().blur).toBeTruthy();
});
});

describe('error display', () => {
it('shows the first error message when errorMessages is non-empty', () => {
renderComponent({ errorMessages: ['Password should be at least 8 characters long'] });
expect(screen.getByText('Password should be at least 8 characters long')).toBeVisible();
});

it('shows no error text when errorMessages is empty', () => {
renderComponent({ errorMessages: [] });
expect(
screen.queryByText('Password should be at least 8 characters long'),
).not.toBeInTheDocument();
});

it('shows only the first error when multiple messages are provided', () => {
renderComponent({ errorMessages: ['First error', 'Second error'] });
expect(screen.getByText('First error')).toBeVisible();
expect(screen.queryByText('Second error')).not.toBeInTheDocument();
});
});
});
Loading