Skip to content
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
4 changes: 1 addition & 3 deletions src/App.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
<template>
<main>
Vue-Tailwind
</main>
<main>Vue-Tailwind</main>
</template>
85 changes: 85 additions & 0 deletions src/components/DxhTextarea.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<template>
<label :for="id" class="block" data-test="textarea-label">
<span v-if="label">{{ label }}</span>
<div class="relative">
<textarea
:id="id"
v-model="inputValue"
:rows="Number(rows)"
:cols="Number(cols)"
:maxlength="Number(maxlength)"
:placeholder="placeholder"
:hint="hint"
:resize="resize"
:disabled="disabled"
:readonly="readonly"
:autofocus="autofocus"
:required="required"
:clearable="clearable"
class="w-full border px-2 py-1 rounded"
:class="resize ? 'resize-y' : 'resize-none'"
@focus="$emit('focus')"
@blur="$emit('blur')"
@change="$emit('change')"
@keyup.enter="$emit('enter')"
@input="$emit('update:modelValue', inputValue)"
data-test="textarea-element"
/>
<div
v-if="clearable && inputValue"
class="absolute right-1.5 top-0 cursor-pointer"
data-test="textarea-clear-button"
>
<slot name="clear" :onClick="clearInput">
<svg
class="inline"
xmlns="http://www.w3.org/2000/svg"
height="14px"
viewBox="0 0 512 512"
@click="clearInput"
>
<path
d="M256 48a208 208 0 1 1 0 416 208 208 0 1 1 0-416zm0 464A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM175 175c-9.4 9.4-9.4 24.6 0 33.9l47 47-47 47c-9.4 9.4-9.4 24.6 0 33.9s24.6 9.4 33.9 0l47-47 47 47c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9l-47-47 47-47c9.4-9.4 9.4-24.6 0-33.9s-24.6-9.4-33.9 0l-47 47-47-47c-9.4-9.4-24.6-9.4-33.9 0z"
/>
</svg>
</slot>
</div>
</div>
<p v-if="hint" data-test="textarea-hint">{{ hint }}</p>
</label>
</template>

<script setup lang="ts">
import { ref, watch } from 'vue'

interface props {
modelValue: string
label?: string
id?: string
type: string
rows?: any
cols?: any
maxlength?: any
placeholder?: string
hint?: string
disabled?: boolean
resize?: boolean
readonly?: boolean
autofocus?: boolean
required?: boolean
clearable?: boolean
}

const { modelValue } = defineProps<props>()

const emit = defineEmits(['focus', 'blur', 'change', 'enter', 'update:modelValue'])

const inputValue = ref(modelValue)

const clearInput = () => {
inputValue.value = ''
}
watch(inputValue, (newValue: any) => {
emit('change', newValue)
})
</script>
106 changes: 106 additions & 0 deletions src/components/__tests__/DxhTextarea.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import DTextarea from '../DxhTextarea.vue'

describe('DTextarea', () => {
it('renders textarea element with label, placeholder, and hint text', () => {
const wrapper = mount(DTextarea, {
props: {
label: 'My Label',
modelValue: '',
placeholder: 'Enter your text',
hint: 'Hint text'
}
})

const label = wrapper.find('[data-test="textarea-label"]')
expect(label.exists()).toBe(true)

const textarea = wrapper.find('[data-test="textarea-element"]')
expect(textarea.exists()).toBe(true)
expect(textarea.attributes('placeholder')).toBe('Enter your text')

const hint = wrapper.find('[data-test="textarea-hint"]')
expect(hint.exists()).toBe(true)
expect(hint.text()).toBe('Hint text')
})

it('binds textarea value to modelValue prop', () => {
const wrapper = mount(DTextarea, {
props: {
modelValue: 'test'
}
})

const textareaElement = wrapper.find('[data-test="textarea-element"]')
.element as HTMLTextAreaElement
expect(textareaElement.value).toBe('test')
})

it('updates modelValue on textarea input', async () => {
const wrapper = mount(DTextarea, {
props: {
modelValue: ''
}
})

const textareaElement = wrapper.find('[data-test="textarea-element"]')
await textareaElement.setValue('new value')

const updateEvents = wrapper.emitted('update:modelValue')
expect(updateEvents).toBeTruthy()

const firstEventPayload = updateEvents ? updateEvents[0] : []
expect(firstEventPayload).toEqual(['new value'])
})

it('emits focus event when textarea is focused', async () => {
const wrapper = mount(DTextarea, {
props: {
modelValue: ''
}
})

await wrapper.find('[data-test="textarea-element"]').trigger('focus')
expect(wrapper.emitted('focus')).toBeTruthy()
})

it('emits blur event when textarea is blurred', async () => {
const wrapper = mount(DTextarea, {
props: {
modelValue: ''
}
})

await wrapper.find('[data-test="textarea-element"]').trigger('blur')
expect(wrapper.emitted('blur')).toBeTruthy()
})

it('emits enter event when Enter key is pressed', async () => {
const wrapper = mount(DTextarea, {
props: {
modelValue: ''
}
})

await wrapper.find('[data-test="textarea-element"]').trigger('keyup.enter')
expect(wrapper.emitted('enter')).toBeTruthy()
})

it('clears textarea value when clearable and close button is clicked', async () => {
const wrapper = mount(DTextarea, {
props: {
modelValue: 'test',
clearable: true
}
})

const closeButton = wrapper.find('[data-test="textarea-clear-button"]')
expect(closeButton.exists()).toBe(true)

await closeButton.trigger('click')
const textareaElement = wrapper.find('[data-test="textarea-element"]')
.element as HTMLTextAreaElement
expect(textareaElement.value).not.toBe('')
})
})
7 changes: 4 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import DButton from "./components/DButton.vue"
import DInput from "./components/DInput.vue"
import DButton from './components/DButton.vue'
import DInput from './components/DInput.vue'
import DxhTextarea from './components/DxhTextarea.vue'

export default {DButton, DInput}
export default { DButton, DInput, DxhTextarea }