Skip to content

Commit

Permalink
Add tests for icon component
Browse files Browse the repository at this point in the history
  • Loading branch information
Tofandel committed Nov 29, 2024
1 parent 522c85b commit 913f099
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 20 deletions.
1 change: 1 addition & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"groups": ["builtin", "external", "internal", "parent", "sibling", "index"]
}
],
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-non-null-assertion": "off"
}
Expand Down
2 changes: 1 addition & 1 deletion playground/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
:items-to-show="itemsToShow"
:items-to-scroll="itemsToScroll"
:gap="10"
:height="parseInt(height) || 'auto'"
:height="height || 'auto'"
:autoplay="autoplay ? parseInt(autoplay) : null"
:pause-autoplay-on-hover="true"
:wrap-around="wrapAround"
Expand Down
27 changes: 23 additions & 4 deletions src/components/Carousel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,13 +163,32 @@ export default defineComponent({
*/
function updateSlideSize(): void {
if (!viewport.value) return
let multiplierWidth = 1
let multiplierHeight = 1
transformElements.forEach((el) => {
const transformArr = parseTransform(el)

if (transformArr.length == 6) {
multiplierWidth *= transformArr[0]
multiplierHeight *= transformArr[3]
}
})

// Calculate size based on orientation
const { width, height } = viewport.value.getBoundingClientRect()
if (isVertical.value) {
slideSize.value = (height - totalGap.value) / config.itemsToShow
if (config.height !== 'auto') {
let height
if (typeof config.height === 'string' && parseInt(config.height).toString() !== config.height) {
height = viewport.value.getBoundingClientRect().height
} else {
height = config.height
}

slideSize.value = (height / multiplierHeight - totalGap.value) / config.itemsToShow

Check failure on line 187 in src/components/Carousel.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.

Check failure on line 187 in src/components/Carousel.ts

View workflow job for this annotation

GitHub Actions / build (22.x)

The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.
}
} else {
slideSize.value = (width - totalGap.value) / config.itemsToShow
const width = viewport.value.getBoundingClientRect().width
slideSize.value = (width / multiplierWidth - totalGap.value) / config.itemsToShow
}
}

Expand Down Expand Up @@ -598,7 +617,7 @@ export default defineComponent({
return `${slideSize.value * config.itemsToShow + totalGap.value}px`
}
return config.height !== 'auto'
? typeof config.height === 'number'
? typeof config.height === 'number' || parseInt(config.height).toString() === config.height
? `${config.height}px`
: config.height
: undefined
Expand Down
29 changes: 14 additions & 15 deletions src/components/Icon.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,45 @@
import { defineComponent, h, inject, PropType } from 'vue'

import { injectCarousel } from '@/injectSymbols'
import { DEFAULT_CONFIG } from '@/partials/defaults'
import icons, { IconName, IconNameValue } from '@/partials/icons'
import { I18nKeys } from '@/types'

function isIconName(candidate: string): candidate is IconName {
return candidate in IconName
}

type IconProps = { name: IconNameValue, title?: string }
export type IconProps = { name: IconNameValue, title?: string }

const iconI18n = <Name extends IconNameValue>(name: Name) => `icon${name.charAt(0).toUpperCase() + name.slice(1)}` as `icon${Capitalize<Name>}`

const validateIconName = (value: IconNameValue) => {
return value && isIconName(value)
}

export default defineComponent({
props: {
name: {
type: String as PropType<IconNameValue>,
required: true,
validator: validateIconName
},
title: {
type: String,
default: (props: {name: IconNameValue}) => props.name ? DEFAULT_CONFIG.i18n[iconI18n(props.name)] : ''
}
},
setup(props: IconProps) {
const carousel = inject(injectCarousel)

if (!carousel) {
return null // Don't render, let vue warn about the missing provide
}
const carousel = inject(injectCarousel, null)

return () => {
const iconName = String(props.name)
const iconI18n = `icon${
iconName.charAt(0).toUpperCase() + iconName.slice(1)
}` as I18nKeys

if (!iconName || !isIconName(iconName)) {
const iconName = props.name
if (!validateIconName(iconName))
return
}

const path = icons[iconName]
const pathEl = h('path', { d: path })

const iconTitle: string = carousel.config.i18n[iconI18n] || props.title || iconName
const iconTitle: string = carousel?.config.i18n[iconI18n(iconName)] || props.title || iconName

const titleEl = h('title', iconTitle)

Expand Down
29 changes: 29 additions & 0 deletions tests/integration/__snapshots__/icon.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`Icon.ts > It should render standalone 1`] = `
"<svg class="carousel__icon" viewBox="0 0 24 24" role="img" aria-label="Arrow pointing upwards">
<title>Arrow pointing upwards</title>
<path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"></path>
</svg>"
`;

exports[`Icon.ts > It should render standalone 2`] = `
"<svg class="carousel__icon" viewBox="0 0 24 24" role="img" aria-label="Arrow pointing downwards">
<title>Arrow pointing downwards</title>
<path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z"></path>
</svg>"
`;

exports[`Icon.ts > It should render standalone 3`] = `
"<svg class="carousel__icon" viewBox="0 0 24 24" role="img" aria-label="Arrow pointing to the right">
<title>Arrow pointing to the right</title>
<path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z"></path>
</svg>"
`;

exports[`Icon.ts > It should render standalone 4`] = `
"<svg class="carousel__icon" viewBox="0 0 24 24" role="img" aria-label="Arrow pointing to the left">
<title>Arrow pointing to the left</title>
<path d="M15.41 16.59L10.83 12l4.58-4.59L14 6l-6 6 6 6 1.41-1.41z"></path>
</svg>"
`;
38 changes: 38 additions & 0 deletions tests/integration/icon.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { expect, it, describe, beforeAll, afterEach } from 'vitest'

import { mount } from '@vue/test-utils'

import { Icon } from '@/index'
import { IconProps } from '@/components/Icon'
import { IconName } from '../../src/partials/icons'

describe('Icon.ts', () => {
const consoleMock = vi.spyOn(console, 'warn').mockImplementation(() => undefined);

afterEach(() => {
consoleMock.mockReset();
});

it('It should error if no iconName', () => {
const wrapper = mount(Icon, { props: {} })
expect(wrapper.html()).toBe("")
expect(consoleMock).toHaveBeenCalledOnce();
expect(consoleMock.mock.calls[0][0]).toBe('[Vue warn]: Missing required prop: "name"');
})

it('It should error if iconName is invalid', () => {
const wrapper = mount(Icon, { props: { name: 'foo' } })
expect(wrapper.html()).toBe("")
console.log(consoleMock.mock.calls)
expect(consoleMock).toHaveBeenCalledOnce();
expect(consoleMock.mock.calls[0][0]).toBe('[Vue warn]: Invalid prop: custom validator check failed for prop "name".');
})

it('It should render standalone', () => {
Object.values(IconName).forEach((name) => {
const wrapper = mount(Icon, { props: { name: name } as IconProps })
expect(consoleMock).not.toHaveBeenCalled()
expect(wrapper.html()).toMatchSnapshot()
})
})
})

0 comments on commit 913f099

Please sign in to comment.