Skip to content

Commit

Permalink
test: updated tests, partial refactor
Browse files Browse the repository at this point in the history
- Added new tests.
- Removed snapshot based tests in favor of explicit assertions.
- Partial refactor of wave.ts. More work is need to make it testable.
  • Loading branch information
justintaddei committed Jul 10, 2024
1 parent 31cf821 commit 5cc0bc8
Show file tree
Hide file tree
Showing 20 changed files with 197 additions and 69 deletions.
15 changes: 10 additions & 5 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,22 @@
"lineEnding": "crlf",
"lineWidth": 120
},
"organizeImports": { "enabled": true },
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"complexity": {
"noForEach": { "level": "off" },
"useOptionalChain": { "level": "off" }
"noForEach": {
"level": "off"
}
},
"suspicious": {
"noExplicitAny": { "level": "off" }
"noExplicitAny": {
"level": "off"
}
}
}
},
Expand All @@ -30,6 +35,6 @@
}
},
"files": {
"ignore": ["dist/**", "node_modules/**", "nuxt/**", "coverage/**"]
"ignore": ["dist/**", "node_modules/**", "nuxt/**", "coverage/**", ".vscode/**"]
}
}
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const createDirective = (
[hooks.mounted](el, { value = {} }) {
optionMap.set(el, value)

markWaveBoundary(el, (value && value.trigger) ?? globalOptions.trigger)
markWaveBoundary(el, value?.trigger ?? globalOptions.trigger)

el.addEventListener('pointerdown', (event) => {
if (!optionMap.has(el)) return
Expand All @@ -62,7 +62,7 @@ const createDirective = (
},
[hooks.updated](el, { value = {} }) {
optionMap.set(el, value)
markWaveBoundary(el, (value && value.trigger) ?? globalOptions.trigger)
markWaveBoundary(el, value?.trigger ?? globalOptions.trigger)
},
}

Expand Down
4 changes: 4 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type Vector = {
x: number
y: number
}
13 changes: 0 additions & 13 deletions src/utils/__snapshots__/createContainerElement.test.ts.snap

This file was deleted.

7 changes: 0 additions & 7 deletions src/utils/__snapshots__/createWaveElement.test.ts.snap

This file was deleted.

13 changes: 0 additions & 13 deletions src/utils/__snapshots__/markWaveBoundary.test.ts.snap

This file was deleted.

24 changes: 20 additions & 4 deletions src/utils/createContainerElement.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,23 @@
import { expect, test } from 'vitest'
import { describe, expect, test } from 'vitest'
import { createContainer } from './createContainerElement'

test('createContainerElement returns a an element based on `tagName`', () => {
expect(createContainer({} as CSSStyleDeclaration, 'div')).toMatchSnapshot()
expect(createContainer({} as CSSStyleDeclaration, 'span')).toMatchSnapshot()
describe('createContainerElement', () => {
test('returns an element based on `tagName`', () => {
expect(createContainer({} as CSSStyleDeclaration, 'div').tagName).toBe('DIV')
expect(createContainer({} as CSSStyleDeclaration, 'span').tagName).toBe('SPAN')
})

test('returns an element with the correct border radius', () => {
const container = createContainer(
{
borderTopLeftRadius: '10px',
borderTopRightRadius: '20px',
borderBottomLeftRadius: '30px',
borderBottomRightRadius: '40px',
} as CSSStyleDeclaration,
'div'
)

expect(container.style.borderRadius).toBe('10px 20px 40px 30px')
})
})
63 changes: 60 additions & 3 deletions src/utils/createWaveElement.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,64 @@
import { expect, test } from 'vitest'
import { describe, expect, test } from 'vitest'
import type { IVWaveDirectiveOptions } from '../options'
import { createWaveElement } from './createWaveElement'

test('createWaveElement returns a <div>', () => {
expect(createWaveElement(0, 0, 0, {} as IVWaveDirectiveOptions)).toMatchSnapshot()
describe('createWaveElement', () => {
test('returns a <div>', () => {
const waveElement = createWaveElement({ x: 0, y: 0 }, 0, {} as IVWaveDirectiveOptions)

expect(waveElement.tagName).toBe('DIV')
})

test('returns a <div> with the correct position', () => {
const waveElement = createWaveElement({ x: 10, y: 20 }, 0, {} as IVWaveDirectiveOptions)

expect(waveElement.style.top).toBe('20px')
expect(waveElement.style.left).toBe('10px')
})

test('returns a <div> with the correct size', () => {
const waveElement = createWaveElement({ x: 0, y: 0 }, 10, {} as IVWaveDirectiveOptions)

expect(waveElement.style.width).toBe('10px')
expect(waveElement.style.height).toBe('10px')
})

test('returns a <div> with the correct background color', () => {
const waveElement = createWaveElement({ x: 0, y: 0 }, 0, { color: 'red' } as IVWaveDirectiveOptions)

expect(waveElement.style.background).toBe('red')
})

test('returns a <div> with the correct border radius', () => {
const waveElement = createWaveElement({ x: 0, y: 0 }, 0, { color: 'red' } as IVWaveDirectiveOptions)

expect(waveElement.style.borderRadius).toBe('50%')
})

test('returns a <div> with the correct opacity', () => {
const waveElement = createWaveElement({ x: 0, y: 0 }, 0, {
initialOpacity: 0.5,
finalOpacity: 0.5,
} as IVWaveDirectiveOptions)

expect(waveElement.style.opacity).toBe('0.5')
})

test('returns a <div> with the correct transform', () => {
const waveElement = createWaveElement({ x: 0, y: 0 }, 0, {
initialOpacity: 0.5,
finalOpacity: 0.5,
} as IVWaveDirectiveOptions)

expect(waveElement.style.transform).toBe('translate(-50%,-50%) scale(0)')
})

test('returns a <div> with the correct transition', () => {
const waveElement = createWaveElement({ x: 0, y: 0 }, 0, {
duration: 1,
easing: 'ease-in',
} as IVWaveDirectiveOptions)

expect(waveElement.style.transition).toBe('transform 1s ease-in, opacity 1s ease-in')
})
})
3 changes: 2 additions & 1 deletion src/utils/createWaveElement.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { IVWaveDirectiveOptions } from '../options'
import type { Vector } from '../types'

export const createWaveElement = (x: number, y: number, size: number, options: IVWaveDirectiveOptions) => {
export const createWaveElement = ({ x, y }: Vector, size: number, options: IVWaveDirectiveOptions) => {
const waveElement = document.createElement('div')

waveElement.style.position = 'absolute'
Expand Down
4 changes: 2 additions & 2 deletions src/utils/getDistanceToFurthestCorner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ import { expect, test } from 'vitest'
import { getDistanceToFurthestCorner } from './getDistanceToFurthestCorner'

test('getDistanceToFurthestCorner', () => {
expect(getDistanceToFurthestCorner(25, 25, { width: 100, height: 100 } as DOMRect)).toBe(106.06601717798213)
expect(getDistanceToFurthestCorner(25, 25, { width: 30, height: 30 } as DOMRect)).toBe(35.35533905932738)
expect(getDistanceToFurthestCorner({ x: 25, y: 25 }, { width: 100, height: 100 } as DOMRect)).toBe(106.06601717798213)
expect(getDistanceToFurthestCorner({ x: 25, y: 25 }, { width: 30, height: 30 } as DOMRect)).toBe(35.35533905932738)
})
3 changes: 2 additions & 1 deletion src/utils/getDistanceToFurthestCorner.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Vector } from '../types'
import { magnitude } from './magnitude'

export function getDistanceToFurthestCorner(x: number, y: number, { width, height }: DOMRect) {
export function getDistanceToFurthestCorner({ x, y }: Vector, { width, height }: DOMRect) {
const topLeft = magnitude(x, y, 0, 0)
const topRight = magnitude(x, y, width, 0)
const bottomLeft = magnitude(x, y, 0, height)
Expand Down
4 changes: 3 additions & 1 deletion src/utils/getRelativePointer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export const getRelativePointer = ({ x, y }: PointerEvent, { top, left }: DOMRect) => ({
import type { Vector } from '../types'

export const getRelativePointer = ({ x, y }: Vector, { top, left }: DOMRect): Vector => ({
x: x - left,
y: y - top,
})
2 changes: 2 additions & 0 deletions src/utils/magnitude.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ import { magnitude } from './magnitude'

test('magnitude', () => {
expect(magnitude(5, 10, 10, 20)).toBe(11.180339887498949)

expect(magnitude(10, 20, 30, 40)).toBe(28.284271247461902)
})
4 changes: 2 additions & 2 deletions src/utils/markWaveBoundary.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ describe('markWaveBoundary', () => {
test('sets dataset to true when trigger is not an id', () => {
const div = document.createElement('div')
markWaveBoundary(div, 'auto')
expect(div).toMatchSnapshot()
expect(div.dataset.vWaveBoundary).toBe('true')
})
test('sets dataset to the id when the trigger is an id', () => {
const div = document.createElement('div')
markWaveBoundary(div, 'stringId')
expect(div).toMatchSnapshot()
expect(div.dataset.vWaveBoundary).toBe('stringId')
})
})
4 changes: 2 additions & 2 deletions src/utils/markWaveBoundary.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { triggerIsID } from './triggerIsID'

export const markWaveBoundary = (el: HTMLElement, trigger: any) => {
el.dataset.vWaveBoundary = triggerIsID(trigger) ? (trigger as string) : 'true'
export const markWaveBoundary = (el: HTMLElement, trigger: string | boolean) => {
el.dataset.vWaveBoundary = triggerIsID(trigger) ? trigger : 'true'
}
48 changes: 48 additions & 0 deletions src/utils/parentElementStyles.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { describe, expect, test, vi } from 'vitest'
import { restoreParentElementStyles, saveParentElementStyles } from './parentElementStyles'

describe('parentElementStyles', () => {
test('saveParentElementStyles', () => {
const el = document.createElement('div')
el.style.position = 'static'

saveParentElementStyles(el, window.getComputedStyle(el))

expect(el.style.position).toBe('relative')
expect(el.dataset.originalPositionValue).toBe('static')
})

test('restoreParentElementStyles', () => {
const el = document.createElement('div')
el.style.position = 'relative'
el.dataset.originalPositionValue = 'static'

restoreParentElementStyles(el)

expect(el.style.position).toBe('static')
expect(el.dataset.originalPositionValue).toBe(undefined)
})

test.each`
direction
${'top'}
${'left'}
${'right'}
${'bottom'}
`('saveParentElementStyles warns when position is static and $direction is not auto', ({ direction }) => {
const el = document.createElement('div')
el.style.position = 'static'
el.style[direction] = '10px'

console.warn = vi.fn()

saveParentElementStyles(el, window.getComputedStyle(el))

expect(console.warn).toHaveBeenCalledWith(
'[v-wave]:',
el,
`You're using a \`static\` positioned element with a non-auto value (10px) for \`${direction}\`.`,
"It's position will be changed to relative while displaying the wave which might cause the element to visually jump."
)
})
})
21 changes: 21 additions & 0 deletions src/utils/parentElementStyles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export const saveParentElementStyles = (el: HTMLElement, computedStyles: CSSStyleDeclaration) => {
if (computedStyles.position === 'static') {
;(['top', 'left', 'right', 'bottom'] as const).forEach((dir) => {
if (computedStyles[dir] && computedStyles[dir] !== 'auto')
console.warn(
'[v-wave]:',
el,
`You're using a \`static\` positioned element with a non-auto value (${computedStyles[dir]}) for \`${dir}\`.`,
"It's position will be changed to relative while displaying the wave which might cause the element to visually jump."
)
})

el.dataset.originalPositionValue = el.style.position
el.style.position = 'relative'
}
}

export const restoreParentElementStyles = (el: HTMLElement) => {
el.style.position = el.dataset.originalPositionValue ?? ''
delete el.dataset.originalPositionValue
}
2 changes: 1 addition & 1 deletion src/utils/triggerIsID.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { expect, test } from 'vitest'
import { triggerIsID } from './triggerIsID'

test('markWaveBoundary', () => {
test('triggerIsID', () => {
expect(triggerIsID('auto')).toEqual(false)
expect(triggerIsID(true)).toEqual(false)
expect(triggerIsID(false)).toEqual(false)
Expand Down
3 changes: 2 additions & 1 deletion src/utils/triggerIsID.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export const triggerIsID = (trigger: string | boolean) => typeof trigger === 'string' && trigger !== 'auto'
export const triggerIsID = (trigger: string | boolean): trigger is string /* and not 'auto' */ =>
typeof trigger === 'string' && trigger !== 'auto'
25 changes: 14 additions & 11 deletions src/wave.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,39 @@
import type { IVWaveDirectiveOptions } from './options'
import type { Vector } from './types'
import { createContainer } from './utils/createContainerElement'
import { createWaveElement } from './utils/createWaveElement'
import { getDistanceToFurthestCorner } from './utils/getDistanceToFurthestCorner'
import { getRelativePointer } from './utils/getRelativePointer'
import { restoreParentElementStyles, saveParentElementStyles } from './utils/parentElementStyles'
import { decrementWaveCount, deleteWaveCount, getWaveCount, incrementWaveCount } from './utils/wave-count'

const wave = (event: PointerEvent, el: HTMLElement, options: IVWaveDirectiveOptions) => {
if (options.disabled) return
// 2.05 is magic.
// Values smaller than this seem to cause the wave to stop
// just short of the edge of the element sometimes.
// (probably to floating point precision)
const SCALE_FACTOR = 2.05

const wave = (screenPos: Vector, el: HTMLElement, options: IVWaveDirectiveOptions) => {
if (options.disabled) return
if (options.respectDisabledAttribute && el.hasAttribute('disabled')) return

const rect = el.getBoundingClientRect()
const computedStyles = window.getComputedStyle(el)

const { x, y } = getRelativePointer(event, rect)
const size = 2.05 * getDistanceToFurthestCorner(x, y, rect) // 2.05 is magic, deal with it.
const relativePos = getRelativePointer(screenPos, rect)
const size = SCALE_FACTOR * getDistanceToFurthestCorner(relativePos, rect)

// We're creating a container for the "wave" with `overflow: hidden`
// because if we were to set `overflow: hidden` on `el` we
// risk altering its appearance.
const waveContainer = createContainer(computedStyles, options.tagName)
const waveEl = createWaveElement(x, y, size, options)
const waveEl = createWaveElement(relativePos, size, options)

// Keep track of how many waves are active on this element.
incrementWaveCount(el)

// We reply on absolute positioning, so we need to make sure `el`'s position is non-static
let originalPositionValue = ''
if (computedStyles.position === 'static') {
if (el.style.position) originalPositionValue = el.style.position
el.style.position = 'relative'
}
saveParentElementStyles(el, computedStyles)

waveContainer.appendChild(waveEl)
el.appendChild(waveContainer)
Expand Down Expand Up @@ -58,7 +61,7 @@ const wave = (event: PointerEvent, el: HTMLElement, options: IVWaveDirectiveOpti
if (getWaveCount(el) === 0) {
deleteWaveCount(el)
// Only reset the style after all active waves have been removed
el.style.position = originalPositionValue
restoreParentElementStyles(el)
}
}, options.dissolveDuration * 1000)
}
Expand Down

0 comments on commit 5cc0bc8

Please sign in to comment.