Skip to content
Merged
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
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"extends": ["custom", "plugin:storybook/recommended"],
"rules": {
"vue/one-component-per-file": "off"
// "no-console": "off"
},
"overrides": [
{
Expand Down
4 changes: 2 additions & 2 deletions packages/components/checkbox/src/bubbleInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const bubbleInput = defineComponent({
const _ref = ref<HTMLInputElement>()

const prevChecked = usePrevious(checked)
const controlSize = useSize(control)
const controlSize = computed(() => useSize(control))

watchEffect(() => {
const input = _ref.value!
Expand All @@ -72,7 +72,7 @@ const bubbleInput = defineComponent({
'ref': _ref,
'style': {
...inputAttrs.style as any,
...controlSize,
...controlSize.value,
position: 'absolute',
pointerEvents: 'none',
opacity: 0,
Expand Down
91 changes: 91 additions & 0 deletions packages/components/checkbox/src/checkbox.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { mount } from '@vue/test-utils'
import { describe, expect, it } from 'vitest'
import type { Component } from 'vue'
import { h, ref } from 'vue'
import { OkuCheckbox } from './checkbox'
import { OkuCheckboxIndicator } from './checkboxIndicator'

const component = {
setup(props, { attrs, slots }) {
return () => h(OkuCheckbox, { ...attrs }, slots)
},
} as Component

const componentVModel = {
setup(props, { attrs }) {
const checked = ref(false)
return () => h(OkuCheckbox, {
...attrs,
'modelValue': checked.value,
'onUpdate:modelValue': e => checked.value = e,
}, {
default: () => h(OkuCheckboxIndicator),
})
},
} as Component

const componentChecked = {
setup(props, { attrs }) {
const checked = ref(false)
return () => h(OkuCheckbox, {
...attrs,
checked: checked.value,
onCheckedChange: e => checked.value = e,
}, {
default: () => h(OkuCheckboxIndicator),
})
},
} as Component

describe('OkuCheckbox', () => {
it('renders the component correctly', () => {
const wrapper = mount(component)
expect(wrapper.exists()).toBe(true)
})

it('renders the component correctly with a label', () => {
const wrapper = mount(component, {
slots: {
default: 'Label',
},
})

expect(wrapper.html()).toContain('<button type="button" role="checkbox" data-state="unchecked">Label</button>')
})

it('can be checked', async () => {
const wrapper = mount(componentVModel)
expect(wrapper.html()).toContain(`<button type="button" role="checkbox" aria-checked="false" data-state="unchecked">
<!---->
</button>`)

const button = wrapper.find('button')
await button.trigger('click')

expect(wrapper.html()).toContain('<button type="button" role="checkbox" aria-checked="true" data-state="checked"><span data-state="checked" style="pointer-events: none;"><!----></span></button>')

await button.trigger('click')

expect(wrapper.html()).toContain(`<button type="button" role="checkbox" aria-checked="false" data-state="unchecked">
<!---->
</button>`)
})

it('can be checked with checked prop', async () => {
const wrapper = mount(componentChecked)
expect(wrapper.html()).toContain(`<button type="button" role="checkbox" aria-checked="false" data-state="unchecked">
<!---->
</button>`)

const button = wrapper.find('button')
await button.trigger('click')

expect(wrapper.html()).toContain('<button type="button" role="checkbox" aria-checked="true" data-state="checked"><span data-state="checked" style="pointer-events: none;"><!----></span></button>')

await button.trigger('click')

expect(wrapper.html()).toContain(`<button type="button" role="checkbox" aria-checked="false" data-state="unchecked">
<!---->
</button>`)
})
})
26 changes: 11 additions & 15 deletions packages/components/checkbox/src/checkbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,6 @@ const checkboxProps = {
type: Boolean as PropType<boolean | undefined>,
default: undefined,
},
onCheckedChange: {
type: Function as PropType<(checked: CheckedState) => void>,
},
name: {
type: String as PropType<string | undefined>,
default: undefined,
Expand All @@ -84,7 +81,7 @@ const Checkbox = defineComponent({
...scopeCheckboxProps,
...primitiveProps,
},
emits: ['update:modelValue'],
emits: ['update:modelValue', 'checkedChange'],
setup(props, { attrs, slots, emit }) {
const {
checked: checkedProp,
Expand All @@ -106,13 +103,18 @@ const Checkbox = defineComponent({
const hasConsumerStoppedPropagationRef = ref(false)

const modelValue = useModel(props, 'modelValue')
const proxyChecked = computed({
get: () => modelValue.value !== undefined ? modelValue.value : checkedProp.value !== undefined ? checkedProp.value : undefined,
set: () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This required?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you can solve it another way, you can pass the tests without changing them.

},
})

const { state, updateValue } = useControllable({
prop: computed(() => modelValue.value || checkedProp.value),
prop: computed(() => proxyChecked.value),
defaultProp: computed(() => defaultChecked.value),
onChange: (result: any) => {
emit('update:modelValue', result)
props.onCheckedChange?.(result)
emit('checkedChange', result)
},
})

Expand Down Expand Up @@ -143,7 +145,7 @@ const Checkbox = defineComponent({
'role': 'checkbox',
'aria-checked': isIndeterminate(state.value) ? 'mixed' : state.value as any,
'aria-required': required.value,
'data-state': getState(state.value as any),
'data-state': computed(() => getState(state.value)).value,
'data-disabled': disabled.value ? '' : undefined,
'disabled': disabled.value,
'value': value.value,
Expand All @@ -156,16 +158,10 @@ const Checkbox = defineComponent({
event.preventDefault()
}),
'onClick': composeEventHandlers(checkboxProps.onClick, (event) => {
const data = isIndeterminate(checkedProp.value) ? true : !checkedProp.value
if (state.value === data)
updateValue(!data)
else if (state.value === 'indeterminate')
updateValue(!data)
else
updateValue(data)
updateValue(isIndeterminate(state.value) ? true : !state.value)

if (isFormControl.value) {
// hasConsumerStoppedPropagationRef.value.current = event.isPropagationStopped()
// hasConsumerStoppedPropagationRef.value = event.isPropagationStopped()
// if checkbox is in a form, stop propagation from the button so that we only propagate
// one click event (from the input). We propagate changes from an input so that native
// form validation works and form events reflect checkbox updates.
Expand Down
6 changes: 3 additions & 3 deletions packages/components/checkbox/src/checkboxIndicator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { defineComponent, h, toRefs } from 'vue'
import { computed, defineComponent, h, toRefs } from 'vue'

import { useForwardRef } from '@oku-ui/use-composable'
import { Primitive, primitiveProps } from '@oku-ui/primitive'
Expand Down Expand Up @@ -39,11 +39,11 @@ const checkboxIndicator = defineComponent({
const context = useCheckboxInject(INDICATOR_NAME, props.scopeOkuCheckbox)

const originalReturn = () => h(OkuPresence, {
present: forceMount.value || isIndeterminate(context.state.value) || context.state.value === true,
present: computed(() => forceMount.value || isIndeterminate(context.state.value) || context.state.value === true).value,
}, {
default: () => h(Primitive.span, {
'ref': forwardedRef,
'data-state': getState(context.state.value),
'data-state': computed(() => getState(context.state.value)).value,
'data-disabled': context.disabled?.value ? '' : undefined,
...indicatorAttrs,
'asChild': props.asChild,
Expand Down
Loading