can be checked 1`] = `
@@ -405,9 +108,6 @@ exports[`okuRadioGroup > can be checked 2`] = `
@@ -489,9 +189,6 @@ exports[`okuRadioGroup > can be checked 3`] = `
@@ -528,9 +225,6 @@ exports[`okuRadioGroup > can be checked 4`] = `
diff --git a/packages/components/radio-group/tests/radio-group.test.ts b/packages/components/radio-group/tests/radio-group.test.ts
new file mode 100644
index 000000000..fdc26e710
--- /dev/null
+++ b/packages/components/radio-group/tests/radio-group.test.ts
@@ -0,0 +1,368 @@
+import { defineComponent, ref, watchEffect } from 'vue'
+import { enableAutoUnmount, mount, shallowMount } from '@vue/test-utils'
+import type { DOMWrapper, VueWrapper } from '@vue/test-utils'
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
+import { axe } from 'vitest-axe'
+
+import { OkuRadioGroup, OkuRadioGroupIndicator, OkuRadioGroupItem } from '../src'
+
+import Styled from '../src/stories/Styled.vue'
+import Controlled from '../src/stories/Controlled.vue'
+import Unset from '../src/stories/Unset.vue'
+import WithinForm from '../src/stories/WithinForm.vue'
+import Animated from '../src/stories/Animated.vue'
+import Chromatic from '../src/stories/Chromatic.vue'
+
+enableAutoUnmount(afterEach)
+
+const INDICATOR_TEST_ID_1 = 'radiogroup-indicator-1'
+const INDICATOR_TEST_ID_2 = 'radiogroup-indicator-2'
+
+const onValueChange = vi.fn()
+
+const RadioGroupTest = defineComponent({
+ components: {
+ OkuRadioGroup,
+ OkuRadioGroupItem,
+ OkuRadioGroupIndicator,
+ },
+ props: {
+ defaultValue: String,
+ value: String,
+ disabled: Boolean,
+ },
+ setup() {
+ const containerRef = ref
(null)
+ watchEffect(() => {
+ // We use the `hidden` attribute to hide the nested input from both sighted users and the
+ // accessibility tree. This is perfectly valid so long as users don't override the display of
+ // `hidden` in CSS. Unfortunately axe doesn't recognize this, so we get a violation because the
+ // input doesn't have a label. This adds an additional `aria-hidden` attribute to the input to
+ // get around that.
+ // https://developer.paciellogroup.com/blog/2012/05/html5-accessibility-chops-hidden-and-aria-hidden/
+ containerRef.value?.querySelector('input')?.setAttribute('aria-hidden', 'true')
+ })
+
+ return {
+ containerRef,
+ INDICATOR_TEST_ID_1,
+ INDICATOR_TEST_ID_2,
+ onValueChange,
+ }
+ },
+ template: `
+
+
+
+
+
+
+
+
+
+
+ `,
+})
+
+globalThis.ResizeObserver = class ResizeObserver {
+ cb: any
+ constructor(cb: any) {
+ this.cb = cb
+ }
+
+ observe() {
+ this.cb([{ borderBoxSize: { inlineSize: 0, blockSize: 0 } }])
+ }
+
+ unobserve() { }
+ disconnect() { }
+}
+
+describe('okuRadioGroup', () => {
+ let wrapper: VueWrapper
+ let radio_1: DOMWrapper
+ let radio_2: DOMWrapper
+ // let indicator_2: DOMWrapper
+
+ beforeEach(() => {
+ wrapper = mount(RadioGroupTest, {
+ attachTo: document.body,
+ })
+
+ radio_1 = wrapper.find('[value="1"]')
+ radio_2 = wrapper.find('[value="2"]')
+
+ // indicator_2 = wrapper.find(`[data-testid="${INDICATOR_TEST_ID_2}"]`)
+ })
+
+ it('should render OkuRadioGroup correctly', () => {
+ expect(wrapper.html()).toMatchSnapshot()
+
+ expect(shallowMount(OkuRadioGroup).html()).toMatchSnapshot()
+ })
+
+ it('should render OkuRadioGroupItem correctly', () => {
+ const spy = vi.spyOn(globalThis.console, 'warn').mockImplementation(() => { })
+ const wrapper = () => mount(OkuRadioGroupItem)
+
+ expect(() => wrapper()).toThrowErrorMatchingSnapshot()
+
+ expect(spy).toHaveBeenCalled()
+
+ expect(spy.mock.calls[0][0]).toContain('[Vue warn]: injection "Symbol(OkuRadioGroup)" not found.')
+ })
+
+ it('should render OkuRadioGroupIndicator correctly', () => {
+ const spy = vi.spyOn(globalThis.console, 'warn').mockImplementation(() => { })
+ const wrapper = () => mount(OkuRadioGroupIndicator)
+
+ expect(() => wrapper()).toThrowErrorMatchingSnapshot()
+
+ expect(spy).toHaveBeenCalled()
+
+ expect(spy.mock.calls[0][0]).toContain('[Vue warn]: injection "Symbol(OkuRadio)" not found.')
+ })
+
+ /**
+ * @vitest-environment jsdom
+ */
+ it('should have no accessibility violations', async () => {
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
+
+ describe('when clicking the radio 2', () => {
+ beforeEach(async () => {
+ radio_2.trigger('click')
+ // indicator_2 = wrapper.find(`[data-testid="${INDICATOR_TEST_ID_2}"]`)
+ })
+
+ it('should render a visible indicator', () => {
+ expect(wrapper.find(`[data-testid="${INDICATOR_TEST_ID_2}"]`).isVisible()).toBe(true)
+ })
+
+ describe('and clicking the radio 1', () => {
+ beforeEach(async () => {
+ radio_1.trigger('click')
+ })
+
+ it('should remove the indicator', () => {
+ expect(wrapper.find(`[data-testid="${INDICATOR_TEST_ID_2}"]`).exists()).toBe(false)
+ })
+ })
+ })
+
+ describe('given a disabled Radio', () => {
+ let wrapper: VueWrapper
+
+ beforeEach(() => {
+ wrapper = mount(RadioGroupTest, {
+ props: {
+ disabled: false,
+ },
+ attachTo: document.body,
+ })
+ })
+
+ /**
+ * @vitest-environment jsdom
+ */
+ it('should have no accessibility violations', async () => {
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
+ })
+
+ describe('given an uncontrolled `checked` RadioGroup', () => {
+ let wrapper: VueWrapper
+ let radio_1: DOMWrapper
+ let indicator_2: DOMWrapper
+
+ beforeEach(() => {
+ wrapper = mount(RadioGroupTest, {
+ props: {
+ defaultValue: '1',
+ },
+ attachTo: document.body,
+ })
+
+ radio_1 = wrapper.find('[value="1"]')
+ radio_2 = wrapper.find('[value="2"]')
+
+ indicator_2 = wrapper.find(`[data-testid="${INDICATOR_TEST_ID_2}"]`)
+ })
+
+ /**
+ * @vitest-environment jsdom
+ */
+ it('should have no accessibility violations', async () => {
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
+
+ describe('when clicking the radio', () => {
+ beforeEach(async () => {
+ radio_1.trigger('click')
+ })
+
+ it('should remove the indicator', () => {
+ expect(indicator_2.exists()).toBe(false)
+ })
+
+ it('should call `onValueChange` event', () => {
+ expect(onValueChange).toHaveBeenCalled()
+ })
+ })
+
+ describe('given a controlled `checked` RadioGroup', () => {
+ let wrapper: VueWrapper
+ let radio_1: DOMWrapper
+
+ beforeEach(() => {
+ wrapper = mount(RadioGroupTest, {
+ props: {
+ value: '1',
+ },
+ attachTo: document.body,
+ })
+
+ radio_1 = wrapper.find('[value="1"]')
+ })
+
+ describe('when clicking the radio 1', () => {
+ beforeEach(() => {
+ radio_1.trigger('click')
+ })
+
+ it('should call `onValueChange` event', () => {
+ expect(onValueChange).toHaveBeenCalled()
+ })
+ })
+ })
+ })
+})
+
+describe('okuRadioGroup Stories', () => {
+ describe('styled', () => {
+ let wrapper: VueWrapper>
+
+ beforeEach(async () => {
+ wrapper = shallowMount(Styled, {
+ attachTo: document.body,
+ })
+ })
+
+ /**
+ * @vitest-environment jsdom
+ */
+ it('should have no accessibility violations', async () => {
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
+
+ it('should render correctly', () => {
+ expect(wrapper.html()).toMatchSnapshot()
+ })
+ })
+
+ describe('controlled', () => {
+ let wrapper: VueWrapper>
+
+ beforeEach(async () => {
+ wrapper = shallowMount(Controlled, {
+ attachTo: document.body,
+ })
+ })
+
+ /**
+ * @vitest-environment jsdom
+ */
+ it('should have no accessibility violations', async () => {
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
+
+ it('should render correctly', () => {
+ expect(wrapper.html()).toMatchSnapshot()
+ })
+ })
+
+ describe('unset', () => {
+ let wrapper: VueWrapper>
+
+ beforeEach(async () => {
+ wrapper = shallowMount(Unset, {
+ attachTo: document.body,
+ })
+ })
+
+ /**
+ * @vitest-environment jsdom
+ */
+ it('should have no accessibility violations', async () => {
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
+
+ it('should render correctly', () => {
+ expect(wrapper.html()).toMatchSnapshot()
+ })
+ })
+
+ describe('withinForm', () => {
+ let wrapper: VueWrapper>
+
+ beforeEach(async () => {
+ wrapper = shallowMount(WithinForm, {
+ attachTo: document.body,
+ })
+ })
+
+ /**
+ * @vitest-environment jsdom
+ */
+ it('should have no accessibility violations', async () => {
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
+
+ it('should render correctly', () => {
+ expect(wrapper.html()).toMatchSnapshot()
+ })
+ })
+
+ describe('animated', () => {
+ let wrapper: VueWrapper>
+
+ beforeEach(async () => {
+ wrapper = shallowMount(Animated, {
+ attachTo: document.body,
+ })
+ })
+
+ /**
+ * @vitest-environment jsdom
+ */
+ it('should have no accessibility violations', async () => {
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
+
+ it('should render correctly', () => {
+ expect(wrapper.html()).toMatchSnapshot()
+ })
+ })
+
+ describe('chromatic', () => {
+ let wrapper: VueWrapper>
+
+ beforeEach(async () => {
+ wrapper = shallowMount(Chromatic, {
+ attachTo: document.body,
+ })
+ })
+
+ /**
+ * @vitest-environment jsdom
+ */
+ it('should have no accessibility violations', async () => {
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
+
+ it('should render correctly', () => {
+ expect(wrapper.html()).toMatchSnapshot()
+ })
+ })
+})
diff --git a/packages/components/radio-group/tests/radio.test.ts b/packages/components/radio-group/tests/radio.test.ts
deleted file mode 100644
index ddd4f0503..000000000
--- a/packages/components/radio-group/tests/radio.test.ts
+++ /dev/null
@@ -1,85 +0,0 @@
-import { mount } from '@vue/test-utils'
-import { describe, expect, it } from 'vitest'
-import type { Component } from 'vue'
-import { defineComponent, h } from 'vue'
-import { OkuRadioGroup, OkuRadioGroupIndicator, OkuRadioGroupItem } from '../src/'
-
-const testComponent = defineComponent({
- components: {
- OkuRadioGroup,
- OkuRadioGroupItem,
- OkuRadioGroupIndicator,
- },
- template: `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- `,
-})
-
-const component = {
- setup(props, { attrs, slots }) {
- return () => h(OkuRadioGroup, { ...attrs }, slots)
- },
-} as Component
-
-describe('okuRadioGroup', () => {
- 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.element).toMatchSnapshot()
- })
-
- it('can be checked', async () => {
- const wrapper = mount(testComponent)
- expect(wrapper.element).toMatchSnapshot()
-
- const value2 = wrapper.find('[value="2"]')
- await value2.trigger('click')
- expect(wrapper.element).toMatchSnapshot()
-
- const value3 = wrapper.find('[value="3"]')
- await value3.trigger('click')
- expect(wrapper.element).toMatchSnapshot()
- expect(value3.attributes('aria-checked')).toBe('true')
- expect(value2.attributes('aria-checked')).toBe('false')
-
- const span = value3.find('span')
- expect(span.classes()).toContain('indicator-class')
- expect(span.attributes('data-state')).toBe('checked')
-
- expect(value2.find('span').exists()).toBe(false)
-
- const value1 = wrapper.find('[value="1"]')
- await value1.trigger('click')
- expect(wrapper.element).toMatchSnapshot()
-
- expect(value1.attributes('aria-checked')).toBe('true')
- expect(value2.attributes('aria-checked')).toBe('false')
- expect(value3.attributes('aria-checked')).toBe('false')
-
- expect(value1.find('span').exists()).toBe(true)
- expect(value2.find('span').exists()).toBe(false)
- expect(value3.find('span').exists()).toBe(false)
- })
-})