@shopify/shop-minis-react
Version:
React component library for Shopify Shop Minis with Tailwind CSS v4 support (source-only, requires TypeScript)
430 lines (347 loc) • 11.1 kB
text/typescript
import {renderHook} from '@testing-library/react'
import {describe, expect, it, vi, beforeEach, afterEach} from 'vitest'
import {useDeeplink} from './useDeeplink'
vi.mock('../../utils/parseUrl', () => ({
parseUrl: vi.fn(),
}))
const {parseUrl} = await import('../../utils/parseUrl')
describe('useDeeplink', () => {
const originalMinisParams = (window as any).minisParams
beforeEach(() => {
vi.clearAllMocks()
})
afterEach(() => {
;(window as any).minisParams = originalMinisParams
})
describe('Without initialUrl', () => {
beforeEach(() => {
;(window as any).minisParams = {
initialUrl: undefined,
handle: 'test-mini',
}
})
it('returns undefined values when initialUrl is not provided', () => {
const {result} = renderHook(() => useDeeplink())
expect(result.current).toEqual({
path: undefined,
queryParams: undefined,
hash: undefined,
})
expect(parseUrl).not.toHaveBeenCalled()
})
})
describe('With initialUrl', () => {
beforeEach(() => {
;(window as any).minisParams = {
initialUrl:
'https://shop.app/mini/test-mini/products?category=clothing',
handle: 'test-mini',
}
})
it('parses URL and strips mini handle prefix from pathname', () => {
vi.mocked(parseUrl).mockReturnValue({
pathname: '/mini/test-mini/products',
query: {category: 'clothing'},
hash: '',
} as any)
const {result} = renderHook(() => useDeeplink())
expect(parseUrl).toHaveBeenCalledWith(
'https://shop.app/mini/test-mini/products?category=clothing'
)
expect(result.current).toEqual({
path: '/products',
queryParams: {category: 'clothing'},
hash: '',
})
})
it('returns pathname as-is when it does not contain mini handle prefix', () => {
;(window as any).minisParams = {
initialUrl: 'https://shop.app/some-other-path',
handle: 'test-mini',
}
vi.mocked(parseUrl).mockReturnValue({
pathname: '/some-other-path',
query: {},
hash: '',
} as any)
const {result} = renderHook(() => useDeeplink())
expect(result.current).toEqual({
path: '/some-other-path',
queryParams: {},
hash: '',
})
})
it('handles URL with multiple query parameters', () => {
;(window as any).minisParams = {
initialUrl:
'https://shop.app/mini/test-mini/search?q=shoes&page=2&sort=price',
handle: 'test-mini',
}
vi.mocked(parseUrl).mockReturnValue({
pathname: '/mini/test-mini/search',
query: {q: 'shoes', page: '2', sort: 'price'},
hash: '',
} as any)
const {result} = renderHook(() => useDeeplink())
expect(result.current).toEqual({
path: '/search',
queryParams: {q: 'shoes', page: '2', sort: 'price'},
hash: '',
})
})
it('handles URL with hash', () => {
;(window as any).minisParams = {
initialUrl: 'https://shop.app/mini/test-mini/page#section-2',
handle: 'test-mini',
}
vi.mocked(parseUrl).mockReturnValue({
pathname: '/mini/test-mini/page',
query: {},
hash: '#section-2',
} as any)
const {result} = renderHook(() => useDeeplink())
expect(result.current).toEqual({
path: '/page',
queryParams: {},
hash: '#section-2',
})
})
it('handles URL with both query params and hash', () => {
;(window as any).minisParams = {
initialUrl: 'https://shop.app/mini/test-mini/products?filter=sale#top',
handle: 'test-mini',
}
vi.mocked(parseUrl).mockReturnValue({
pathname: '/mini/test-mini/products',
query: {filter: 'sale'},
hash: '#top',
} as any)
const {result} = renderHook(() => useDeeplink())
expect(result.current).toEqual({
path: '/products',
queryParams: {filter: 'sale'},
hash: '#top',
})
})
it('handles root path after stripping prefix', () => {
;(window as any).minisParams = {
initialUrl: 'https://shop.app/mini/test-mini',
handle: 'test-mini',
}
vi.mocked(parseUrl).mockReturnValue({
pathname: '/mini/test-mini',
query: {},
hash: '',
} as any)
const {result} = renderHook(() => useDeeplink())
expect(result.current).toEqual({
path: '',
queryParams: {},
hash: '',
})
})
it('handles root path with trailing slash', () => {
;(window as any).minisParams = {
initialUrl: 'https://shop.app/mini/test-mini/',
handle: 'test-mini',
}
vi.mocked(parseUrl).mockReturnValue({
pathname: '/mini/test-mini/',
query: {},
hash: '',
} as any)
const {result} = renderHook(() => useDeeplink())
expect(result.current).toEqual({
path: '/',
queryParams: {},
hash: '',
})
})
it('handles nested paths', () => {
;(window as any).minisParams = {
initialUrl:
'https://shop.app/mini/test-mini/category/shoes/product/123',
handle: 'test-mini',
}
vi.mocked(parseUrl).mockReturnValue({
pathname: '/mini/test-mini/category/shoes/product/123',
query: {},
hash: '',
} as any)
const {result} = renderHook(() => useDeeplink())
expect(result.current).toEqual({
path: '/category/shoes/product/123',
queryParams: {},
hash: '',
})
})
it('handles different mini handle', () => {
;(window as any).minisParams = {
initialUrl: 'https://shop.app/mini/another-mini/dashboard',
handle: 'another-mini',
}
vi.mocked(parseUrl).mockReturnValue({
pathname: '/mini/another-mini/dashboard',
query: {},
hash: '',
} as any)
const {result} = renderHook(() => useDeeplink())
expect(result.current).toEqual({
path: '/dashboard',
queryParams: {},
hash: '',
})
})
})
describe('Memoization', () => {
it('returns same reference when dependencies do not change', () => {
;(window as any).minisParams = {
initialUrl: 'https://shop.app/mini/test-mini/products',
handle: 'test-mini',
}
vi.mocked(parseUrl).mockReturnValue({
pathname: '/mini/test-mini/products',
query: {},
hash: '',
} as any)
const {result, rerender} = renderHook(() => useDeeplink())
const firstResult = result.current
rerender()
expect(result.current).toBe(firstResult)
expect(parseUrl).toHaveBeenCalledTimes(1)
})
it('recomputes when initialUrl changes', () => {
;(window as any).minisParams = {
initialUrl: 'https://shop.app/mini/test-mini/products',
handle: 'test-mini',
}
vi.mocked(parseUrl).mockReturnValue({
pathname: '/mini/test-mini/products',
query: {},
hash: '',
} as any)
const {result, rerender} = renderHook(() => useDeeplink())
const firstResult = result.current
// Change initialUrl
;(window as any).minisParams = {
initialUrl: 'https://shop.app/mini/test-mini/cart',
handle: 'test-mini',
}
vi.mocked(parseUrl).mockReturnValue({
pathname: '/mini/test-mini/cart',
query: {},
hash: '',
} as any)
rerender()
expect(result.current).not.toBe(firstResult)
expect(result.current.path).toBe('/cart')
expect(parseUrl).toHaveBeenCalledTimes(2)
})
it('recomputes when handle changes', () => {
;(window as any).minisParams = {
initialUrl: 'https://shop.app/mini/test-mini/products',
handle: 'test-mini',
}
vi.mocked(parseUrl).mockReturnValue({
pathname: '/mini/test-mini/products',
query: {},
hash: '',
} as any)
const {result, rerender} = renderHook(() => useDeeplink())
const firstResult = result.current
// Change handle
;(window as any).minisParams = {
initialUrl: 'https://shop.app/mini/test-mini/products',
handle: 'different-mini',
}
vi.mocked(parseUrl).mockReturnValue({
pathname: '/mini/test-mini/products',
query: {},
hash: '',
} as any)
rerender()
expect(result.current).not.toBe(firstResult)
expect(parseUrl).toHaveBeenCalledTimes(2)
})
})
describe('Edge Cases', () => {
it('handles empty initialUrl string', () => {
;(window as any).minisParams = {
initialUrl: '',
handle: 'test-mini',
}
const {result} = renderHook(() => useDeeplink())
expect(result.current).toEqual({
path: undefined,
queryParams: undefined,
hash: undefined,
})
expect(parseUrl).not.toHaveBeenCalled()
})
it('handles null initialUrl', () => {
;(window as any).minisParams = {
initialUrl: null,
handle: 'test-mini',
}
const {result} = renderHook(() => useDeeplink())
expect(result.current).toEqual({
path: undefined,
queryParams: undefined,
hash: undefined,
})
expect(parseUrl).not.toHaveBeenCalled()
})
it('handles query params with undefined values', () => {
;(window as any).minisParams = {
initialUrl: 'https://shop.app/mini/test-mini/search?q=&page=1',
handle: 'test-mini',
}
vi.mocked(parseUrl).mockReturnValue({
pathname: '/mini/test-mini/search',
query: {q: undefined, page: '1'},
hash: '',
} as any)
const {result} = renderHook(() => useDeeplink())
expect(result.current).toEqual({
path: '/search',
queryParams: {q: undefined, page: '1'},
hash: '',
})
})
it('handles pathname with special characters', () => {
;(window as any).minisParams = {
initialUrl:
'https://shop.app/mini/test-mini/search?q=%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF',
handle: 'test-mini',
}
vi.mocked(parseUrl).mockReturnValue({
pathname: '/mini/test-mini/search',
query: {q: 'S�kao'},
hash: '',
} as any)
const {result} = renderHook(() => useDeeplink())
expect(result.current).toEqual({
path: '/search',
queryParams: {q: 'S�kao'},
hash: '',
})
})
it('handles case where pathname is just the prefix', () => {
;(window as any).minisParams = {
initialUrl: 'https://shop.app/mini/test-mini',
handle: 'test-mini',
}
vi.mocked(parseUrl).mockReturnValue({
pathname: '/mini/test-mini',
query: {},
hash: '',
} as any)
const {result} = renderHook(() => useDeeplink())
expect(result.current).toEqual({
path: '',
queryParams: {},
hash: '',
})
})
})
})