Files
logos-web/apps/web/lib/__tests__/umami-button-tracking.test.ts
jinhojang6 2ede0d4a8c fix(web): separate stacked copy in button tracking event names
Button labels were built from textContent, so a card that stacks a heading
above its CTA produced run-together names like "Explore the TechGet Started".
Build the label from innerText instead and join its rendered lines with the
" - " separator already used by hand-written event names.
2026-08-29 00:30:37 +09:00

177 lines
4.9 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import {
TRACKABLE_BUTTON_SELECTOR,
buildButtonClickEventName,
buildButtonClickEventData,
getButtonTrackingLabel,
getTrackableButton,
shouldTrackButtonClick,
} from '../umami-button-tracking'
interface FakeButtonOptions {
readonly ariaLabel?: string
readonly eventName?: string
readonly disabled?: boolean
readonly excluded?: boolean
readonly excludedAncestor?: boolean
readonly id?: string
readonly innerText?: string
readonly name?: string
readonly nestedEventName?: string
readonly textContent?: string
readonly title?: string
}
const createButton = ({
ariaLabel,
disabled = false,
eventName,
excluded = false,
excludedAncestor = false,
id = '',
innerText,
name,
nestedEventName,
textContent = '',
title,
}: FakeButtonOptions = {}) => {
const attributes = new Map<string, string>()
if (ariaLabel) attributes.set('aria-label', ariaLabel)
if (eventName) attributes.set('data-umami-event-name', eventName)
if (excluded) attributes.set('data-umami-button-tracking', 'off')
if (name) attributes.set('name', name)
if (title) attributes.set('title', title)
return {
classList: {
contains: (className: string) => className === 'disabled' && disabled,
},
closest: (selector: string) =>
selector === '[data-umami-button-tracking="off"]' && excludedAncestor
? {}
: null,
disabled,
getAttribute: (name: string) => attributes.get(name) ?? null,
id,
innerText,
querySelector: (selector: string) =>
selector === '[data-umami-event-name]' && nestedEventName
? {
getAttribute: (name: string) =>
name === 'data-umami-event-name' ? nestedEventName : null,
}
: null,
textContent,
} as unknown as HTMLElement
}
describe('getTrackableButton', () => {
it('finds the closest button or link target', () => {
const button = createButton()
const target = {
closest: (selector: string) =>
selector === TRACKABLE_BUTTON_SELECTOR ? button : null,
} as unknown as Element
expect(getTrackableButton(target)).toBe(button)
})
it('returns null for non-DOM targets', () => {
expect(getTrackableButton(null)).toBeNull()
expect(getTrackableButton({})).toBeNull()
})
})
describe('shouldTrackButtonClick', () => {
it('tracks enabled buttons and links by default', () => {
expect(shouldTrackButtonClick(createButton())).toBe(true)
})
it('skips disabled and explicitly excluded elements', () => {
expect(shouldTrackButtonClick(createButton({ disabled: true }))).toBe(false)
expect(shouldTrackButtonClick(createButton({ excluded: true }))).toBe(false)
expect(
shouldTrackButtonClick(createButton({ excludedAncestor: true }))
).toBe(false)
})
})
describe('getButtonTrackingLabel', () => {
it('uses an explicit Umami event name before other labels', () => {
expect(
getButtonTrackingLabel(
createButton({
eventName: 'Run a Node',
id: 'node-card',
textContent: 'Run a node in less than 10 minutes Using CLI',
})
)
).toBe('Run a Node')
})
it('uses id before visible text', () => {
expect(
getButtonTrackingLabel(
createButton({ id: 'join-cta', textContent: 'Join the movement' })
)
).toBe('join-cta')
})
it('uses a nested explicit event name for generated interactive wrappers', () => {
expect(
getButtonTrackingLabel(
createButton({
nestedEventName: 'Open map cluster - 2 circles',
textContent: '2',
})
)
).toBe('Open map cluster - 2 circles')
})
it('joins the rendered lines of stacked copy with a dash', () => {
expect(
getButtonTrackingLabel(
createButton({
innerText: 'Explore the Tech\nGet Started',
textContent: 'Explore the TechGet Started',
})
)
).toBe('Explore the Tech - Get Started')
})
it('falls back through text, aria-label, name, and title', () => {
expect(
getButtonTrackingLabel(createButton({ textContent: ' Learn more ' }))
).toBe('Learn more')
expect(
getButtonTrackingLabel(createButton({ ariaLabel: 'Open menu' }))
).toBe('Open menu')
expect(getButtonTrackingLabel(createButton({ name: 'newsletter' }))).toBe(
'newsletter'
)
expect(
getButtonTrackingLabel(createButton({ title: 'External link' }))
).toBe('External link')
expect(getButtonTrackingLabel(null)).toBeNull()
expect(getButtonTrackingLabel(createButton())).toBeNull()
})
})
describe('buildButtonClickEventName', () => {
it('returns the extracted label', () => {
expect(
buildButtonClickEventName(createButton({ textContent: 'Subscribe' }))
).toBe('Subscribe')
})
})
describe('buildButtonClickEventData', () => {
it('keeps pathname context in event data', () => {
expect(buildButtonClickEventData('/builders-hub')).toEqual({
source: '/builders-hub',
})
})
})