@chronogrove/ui 0.82.3 → 0.83.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +9 -1
- package/src/__snapshots__/image-thumbnails.spec.js.snap +61 -0
- package/src/__snapshots__/thumbnail-strip.spec.js.snap +61 -0
- package/src/image-thumbnails.js +89 -0
- package/src/image-thumbnails.spec.js +95 -0
- package/src/thumbnail-strip.js +72 -0
- package/src/thumbnail-strip.spec.js +83 -0
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ Prefer deep imports so bundles stay lean:
|
|
|
44
44
|
| `@chronogrove/ui/widget-header` | Widget section title row (optional Font Awesome icon, aside slot, optional metrics) |
|
|
45
45
|
| `@chronogrove/ui/gatsby` | Color-mode Gatsby SSR/browser helpers |
|
|
46
46
|
|
|
47
|
-
**Additional subpaths:** The table lists the most common entry points. Also published: **`pagination`** (full bar; composes **`pagination-button`**), **`category-label`**, **`metric-badge`**, **`metric-card`**, **`muted-card-footer`**, **`status-card`**, **`widget-section`**, **`widget-call-to-action`**, **`external-link-icon
|
|
47
|
+
**Additional subpaths:** The table lists the most common entry points. Also published: **`pagination`** (full bar; composes **`pagination-button`**), **`category-label`**, **`metric-badge`**, **`metric-card`**, **`muted-card-footer`**, **`status-card`**, **`widget-section`**, **`widget-call-to-action`**, **`external-link-icon`**, **`thumbnail-strip`**, **`image-thumbnails`** (`optimizeSrc` for CDN resizing; Gatsby passes a Cloudinary helper). The authoritative list is **`package.json`** → **`exports`**.
|
|
48
48
|
|
|
49
49
|
## Next.js (App Router)
|
|
50
50
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chronogrove/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.83.0",
|
|
4
4
|
"description": "Chronogrove Theme UI theme, color mode helpers, and shared UI primitives",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -138,6 +138,14 @@
|
|
|
138
138
|
"import": "./src/action-card-layout.js",
|
|
139
139
|
"default": "./src/action-card-layout.js"
|
|
140
140
|
},
|
|
141
|
+
"./thumbnail-strip": {
|
|
142
|
+
"import": "./src/thumbnail-strip.js",
|
|
143
|
+
"default": "./src/thumbnail-strip.js"
|
|
144
|
+
},
|
|
145
|
+
"./image-thumbnails": {
|
|
146
|
+
"import": "./src/image-thumbnails.js",
|
|
147
|
+
"default": "./src/image-thumbnails.js"
|
|
148
|
+
},
|
|
141
149
|
"./gatsby": {
|
|
142
150
|
"import": "./src/gatsby/index.js",
|
|
143
151
|
"default": "./src/gatsby/index.js"
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
|
2
|
+
|
|
3
|
+
exports[`ImageThumbnails matches snapshot with custom maxImages 1`] = `
|
|
4
|
+
<DocumentFragment>
|
|
5
|
+
<div
|
|
6
|
+
class="css-1ud2rd2"
|
|
7
|
+
>
|
|
8
|
+
<div
|
|
9
|
+
class="css-1jg6seg"
|
|
10
|
+
>
|
|
11
|
+
<div
|
|
12
|
+
class="css-m0n9e0"
|
|
13
|
+
/>
|
|
14
|
+
</div>
|
|
15
|
+
<div
|
|
16
|
+
class="css-1da34oq"
|
|
17
|
+
>
|
|
18
|
+
<div
|
|
19
|
+
class="css-1yk8ryr"
|
|
20
|
+
/>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</DocumentFragment>
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
exports[`ImageThumbnails matches snapshot with default props 1`] = `
|
|
27
|
+
<DocumentFragment>
|
|
28
|
+
<div
|
|
29
|
+
class="css-1ud2rd2"
|
|
30
|
+
>
|
|
31
|
+
<div
|
|
32
|
+
class="css-1jg6seg"
|
|
33
|
+
>
|
|
34
|
+
<div
|
|
35
|
+
class="css-m0n9e0"
|
|
36
|
+
/>
|
|
37
|
+
</div>
|
|
38
|
+
<div
|
|
39
|
+
class="css-1da34oq"
|
|
40
|
+
>
|
|
41
|
+
<div
|
|
42
|
+
class="css-1yk8ryr"
|
|
43
|
+
/>
|
|
44
|
+
</div>
|
|
45
|
+
<div
|
|
46
|
+
class="css-1jg6seg"
|
|
47
|
+
>
|
|
48
|
+
<div
|
|
49
|
+
class="css-1udw8eu"
|
|
50
|
+
/>
|
|
51
|
+
</div>
|
|
52
|
+
<div
|
|
53
|
+
class="css-1da34oq"
|
|
54
|
+
>
|
|
55
|
+
<div
|
|
56
|
+
class="css-dvh21o"
|
|
57
|
+
/>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</DocumentFragment>
|
|
61
|
+
`;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
|
2
|
+
|
|
3
|
+
exports[`ThumbnailStrip matches snapshot with custom maxImages and size 1`] = `
|
|
4
|
+
<DocumentFragment>
|
|
5
|
+
<div
|
|
6
|
+
class="css-dpp4jp"
|
|
7
|
+
>
|
|
8
|
+
<div
|
|
9
|
+
class="css-1p4ud4e"
|
|
10
|
+
>
|
|
11
|
+
<div
|
|
12
|
+
class="css-m0n9e0"
|
|
13
|
+
/>
|
|
14
|
+
</div>
|
|
15
|
+
<div
|
|
16
|
+
class="css-zlkqv4"
|
|
17
|
+
>
|
|
18
|
+
<div
|
|
19
|
+
class="css-1yk8ryr"
|
|
20
|
+
/>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</DocumentFragment>
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
exports[`ThumbnailStrip matches snapshot with default props 1`] = `
|
|
27
|
+
<DocumentFragment>
|
|
28
|
+
<div
|
|
29
|
+
class="css-1httq4l"
|
|
30
|
+
>
|
|
31
|
+
<div
|
|
32
|
+
class="css-1jl8pr3"
|
|
33
|
+
>
|
|
34
|
+
<div
|
|
35
|
+
class="css-m0n9e0"
|
|
36
|
+
/>
|
|
37
|
+
</div>
|
|
38
|
+
<div
|
|
39
|
+
class="css-f02gwb"
|
|
40
|
+
>
|
|
41
|
+
<div
|
|
42
|
+
class="css-1yk8ryr"
|
|
43
|
+
/>
|
|
44
|
+
</div>
|
|
45
|
+
<div
|
|
46
|
+
class="css-44fsif"
|
|
47
|
+
>
|
|
48
|
+
<div
|
|
49
|
+
class="css-1udw8eu"
|
|
50
|
+
/>
|
|
51
|
+
</div>
|
|
52
|
+
<div
|
|
53
|
+
class="css-ladzy1"
|
|
54
|
+
>
|
|
55
|
+
<div
|
|
56
|
+
class="css-dvh21o"
|
|
57
|
+
/>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</DocumentFragment>
|
|
61
|
+
`;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Box } from '@theme-ui/components'
|
|
3
|
+
|
|
4
|
+
/** Default cap on thumbnails shown when `images` exceeds this count. */
|
|
5
|
+
export const IMAGE_THUMBNAILS_DEFAULT_MAX = 4
|
|
6
|
+
|
|
7
|
+
/** Thumbnail box size (px); retina-friendly optimizers typically use ~2× for width/height. */
|
|
8
|
+
export const IMAGE_THUMBNAILS_SIZE_PX = 64
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Pass-through optimizer (CDN-agnostic default).
|
|
12
|
+
*
|
|
13
|
+
* @param {string | null | undefined} src
|
|
14
|
+
* @returns {string | null | undefined}
|
|
15
|
+
*/
|
|
16
|
+
export const IMAGE_THUMBNAILS_DEFAULT_OPTIMIZE_SRC = src => src
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Horizontal row of small circular image previews — e.g. post cards (`Recap`).
|
|
20
|
+
* Supply `optimizeSrc` for CDN-specific resizing (Gatsby theme uses a Cloudinary helper).
|
|
21
|
+
*
|
|
22
|
+
* @param {object} props
|
|
23
|
+
* @param {Array<string | null | undefined>} [props.images]
|
|
24
|
+
* @param {number} [props.maxImages]
|
|
25
|
+
* @param {(src: string) => string | null | undefined} [props.optimizeSrc]
|
|
26
|
+
*/
|
|
27
|
+
const ImageThumbnails = ({
|
|
28
|
+
images = [],
|
|
29
|
+
maxImages = IMAGE_THUMBNAILS_DEFAULT_MAX,
|
|
30
|
+
optimizeSrc = IMAGE_THUMBNAILS_DEFAULT_OPTIMIZE_SRC
|
|
31
|
+
}) => {
|
|
32
|
+
if (!images || images.length === 0) {
|
|
33
|
+
return null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const displayImages = images.slice(0, maxImages)
|
|
37
|
+
const size = IMAGE_THUMBNAILS_SIZE_PX
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<Box
|
|
41
|
+
sx={{
|
|
42
|
+
display: 'flex',
|
|
43
|
+
gap: 2,
|
|
44
|
+
mb: 2,
|
|
45
|
+
flexWrap: 'wrap'
|
|
46
|
+
}}
|
|
47
|
+
>
|
|
48
|
+
{displayImages.map((src, index) => {
|
|
49
|
+
const input = typeof src === 'string' ? src : ''
|
|
50
|
+
const optimized = optimizeSrc(input)
|
|
51
|
+
const url = optimized !== null && optimized !== undefined ? String(optimized) : ''
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<Box
|
|
55
|
+
key={index}
|
|
56
|
+
sx={{
|
|
57
|
+
width: `${size}px`,
|
|
58
|
+
height: `${size}px`,
|
|
59
|
+
borderRadius: '50%',
|
|
60
|
+
overflow: 'hidden',
|
|
61
|
+
flexShrink: 0,
|
|
62
|
+
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)',
|
|
63
|
+
border: '2px solid',
|
|
64
|
+
borderColor: 'background',
|
|
65
|
+
transform: `translateY(${index % 2 === 0 ? 0 : 5}px)`
|
|
66
|
+
}}
|
|
67
|
+
>
|
|
68
|
+
<Box
|
|
69
|
+
sx={{
|
|
70
|
+
width: '100%',
|
|
71
|
+
height: '100%',
|
|
72
|
+
...(url
|
|
73
|
+
? {
|
|
74
|
+
backgroundImage: `url(${url})`,
|
|
75
|
+
backgroundSize: 'cover',
|
|
76
|
+
backgroundPosition: 'center',
|
|
77
|
+
backgroundRepeat: 'no-repeat'
|
|
78
|
+
}
|
|
79
|
+
: {})
|
|
80
|
+
}}
|
|
81
|
+
/>
|
|
82
|
+
</Box>
|
|
83
|
+
)
|
|
84
|
+
})}
|
|
85
|
+
</Box>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export default ImageThumbnails
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { render } from '@testing-library/react'
|
|
2
|
+
import { ThemeUIProvider } from 'theme-ui'
|
|
3
|
+
import React from 'react'
|
|
4
|
+
|
|
5
|
+
import ImageThumbnails from './image-thumbnails.js'
|
|
6
|
+
|
|
7
|
+
const stubTheme = { colors: { background: '#fff' } }
|
|
8
|
+
|
|
9
|
+
const wrap = ui => render(<ThemeUIProvider theme={stubTheme}>{ui}</ThemeUIProvider>)
|
|
10
|
+
|
|
11
|
+
describe('ImageThumbnails', () => {
|
|
12
|
+
const sampleImages = [
|
|
13
|
+
'https://example.com/image1.jpg',
|
|
14
|
+
'https://example.com/image2.jpg',
|
|
15
|
+
'https://example.com/image3.jpg',
|
|
16
|
+
'https://example.com/image4.jpg',
|
|
17
|
+
'https://example.com/image5.jpg'
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
it('renders thumbnails when images are provided', () => {
|
|
21
|
+
const { container } = wrap(<ImageThumbnails images={sampleImages} />)
|
|
22
|
+
|
|
23
|
+
const wrapper = container.firstChild
|
|
24
|
+
expect(wrapper).toBeTruthy()
|
|
25
|
+
expect(wrapper.children).toHaveLength(4)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('renders correct number of thumbnails based on maxImages prop', () => {
|
|
29
|
+
const { container } = wrap(<ImageThumbnails images={sampleImages} maxImages={3} />)
|
|
30
|
+
|
|
31
|
+
const wrapper = container.firstChild
|
|
32
|
+
expect(wrapper).toBeTruthy()
|
|
33
|
+
expect(wrapper.children).toHaveLength(3)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('returns null when images array is empty', () => {
|
|
37
|
+
const { container } = wrap(<ImageThumbnails images={[]} />)
|
|
38
|
+
expect(container.firstChild).toBeNull()
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('returns null when images is null', () => {
|
|
42
|
+
const { container } = wrap(<ImageThumbnails images={null} />)
|
|
43
|
+
expect(container.firstChild).toBeNull()
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('returns null when images is undefined', () => {
|
|
47
|
+
const { container } = wrap(<ImageThumbnails />)
|
|
48
|
+
expect(container.firstChild).toBeNull()
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('renders fewer thumbnails when fewer images are provided than maxImages', () => {
|
|
52
|
+
const twoImages = ['https://example.com/image1.jpg', 'https://example.com/image2.jpg']
|
|
53
|
+
const { container } = wrap(<ImageThumbnails images={twoImages} maxImages={4} />)
|
|
54
|
+
|
|
55
|
+
const wrapper = container.firstChild
|
|
56
|
+
expect(wrapper).toBeTruthy()
|
|
57
|
+
expect(wrapper.children).toHaveLength(2)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('uses optimizeSrc when provided', () => {
|
|
61
|
+
const optimizer = jest.fn(s => `${s}?x=1`)
|
|
62
|
+
const { container } = wrap(<ImageThumbnails images={['https://example.com/a.jpg']} optimizeSrc={optimizer} />)
|
|
63
|
+
expect(optimizer).toHaveBeenCalledWith('https://example.com/a.jpg')
|
|
64
|
+
expect(container.firstChild).toBeTruthy()
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('omits inner background when optimizeSrc returns null', () => {
|
|
68
|
+
const { container } = wrap(<ImageThumbnails images={['https://example.com/z.jpg']} optimizeSrc={() => null} />)
|
|
69
|
+
expect(container.firstChild?.firstChild?.firstChild).toBeTruthy()
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('omits inner background when optimizeSrc returns undefined', () => {
|
|
73
|
+
const { container } = wrap(<ImageThumbnails images={['https://example.com/z.jpg']} optimizeSrc={() => undefined} />)
|
|
74
|
+
expect(container.firstChild?.firstChild?.firstChild).toBeTruthy()
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('handles null and non-string slots as empty thumbnails (same slot count)', () => {
|
|
78
|
+
const mixedImages = ['https://example.com/image1.jpg', null, 'https://example.com/image2.jpg', undefined]
|
|
79
|
+
const { container } = wrap(<ImageThumbnails images={mixedImages} maxImages={4} />)
|
|
80
|
+
|
|
81
|
+
const wrapper = container.firstChild
|
|
82
|
+
expect(wrapper).toBeTruthy()
|
|
83
|
+
expect(wrapper.children).toHaveLength(4)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('matches snapshot with default props', () => {
|
|
87
|
+
const { asFragment } = wrap(<ImageThumbnails images={sampleImages} />)
|
|
88
|
+
expect(asFragment()).toMatchSnapshot()
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('matches snapshot with custom maxImages', () => {
|
|
92
|
+
const { asFragment } = wrap(<ImageThumbnails images={sampleImages} maxImages={2} />)
|
|
93
|
+
expect(asFragment()).toMatchSnapshot()
|
|
94
|
+
})
|
|
95
|
+
})
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Box } from '@theme-ui/components'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Compact vertical strip of thumbnail images — staggered/offset for card side rails.
|
|
6
|
+
*/
|
|
7
|
+
const ThumbnailStrip = ({ images = [], maxImages = 4, size = 36 }) => {
|
|
8
|
+
if (!images || images.length === 0) {
|
|
9
|
+
return null
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const displayImages = images.slice(0, maxImages)
|
|
13
|
+
const overlap = size * 0.35 // 35% overlap for compact stacking
|
|
14
|
+
const stripW = size + 8
|
|
15
|
+
const stripH = displayImages.length * (size - overlap) + overlap
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<Box
|
|
19
|
+
sx={{
|
|
20
|
+
display: 'flex',
|
|
21
|
+
flexDirection: 'column',
|
|
22
|
+
alignItems: 'center',
|
|
23
|
+
position: 'relative',
|
|
24
|
+
// Theme UI maps bare numbers in `sx` to theme scales — use explicit px for layout math.
|
|
25
|
+
width: `${stripW}px`,
|
|
26
|
+
height: `${stripH}px`,
|
|
27
|
+
flexShrink: 0
|
|
28
|
+
}}
|
|
29
|
+
>
|
|
30
|
+
{displayImages.map((src, index) => (
|
|
31
|
+
<Box
|
|
32
|
+
key={index}
|
|
33
|
+
sx={{
|
|
34
|
+
position: 'absolute',
|
|
35
|
+
top: `${index * (size - overlap)}px`,
|
|
36
|
+
left: index % 2 === 0 ? 0 : '8px',
|
|
37
|
+
width: `${size}px`,
|
|
38
|
+
height: `${size}px`,
|
|
39
|
+
borderRadius: '6px',
|
|
40
|
+
overflow: 'hidden',
|
|
41
|
+
boxShadow: '0 2px 6px rgba(0, 0, 0, 0.12)',
|
|
42
|
+
border: '2px solid',
|
|
43
|
+
borderColor: 'background',
|
|
44
|
+
transition: 'transform 0.2s ease',
|
|
45
|
+
zIndex: displayImages.length - index,
|
|
46
|
+
'&:hover': {
|
|
47
|
+
transform: 'scale(1.08)',
|
|
48
|
+
zIndex: displayImages.length + 1
|
|
49
|
+
}
|
|
50
|
+
}}
|
|
51
|
+
>
|
|
52
|
+
<Box
|
|
53
|
+
sx={{
|
|
54
|
+
width: '100%',
|
|
55
|
+
height: '100%',
|
|
56
|
+
...(typeof src === 'string' && src
|
|
57
|
+
? {
|
|
58
|
+
backgroundImage: `url(${src})`,
|
|
59
|
+
backgroundSize: 'cover',
|
|
60
|
+
backgroundPosition: 'center',
|
|
61
|
+
backgroundRepeat: 'no-repeat'
|
|
62
|
+
}
|
|
63
|
+
: {})
|
|
64
|
+
}}
|
|
65
|
+
/>
|
|
66
|
+
</Box>
|
|
67
|
+
))}
|
|
68
|
+
</Box>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export default ThumbnailStrip
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { render } from '@testing-library/react'
|
|
2
|
+
import { ThemeUIProvider } from 'theme-ui'
|
|
3
|
+
import React from 'react'
|
|
4
|
+
|
|
5
|
+
import ThumbnailStrip from './thumbnail-strip.js'
|
|
6
|
+
|
|
7
|
+
const stubTheme = { colors: { background: '#fff' } }
|
|
8
|
+
|
|
9
|
+
const wrap = ui => render(<ThemeUIProvider theme={stubTheme}>{ui}</ThemeUIProvider>)
|
|
10
|
+
|
|
11
|
+
describe('ThumbnailStrip', () => {
|
|
12
|
+
const sampleImages = [
|
|
13
|
+
'https://example.com/image1.jpg',
|
|
14
|
+
'https://example.com/image2.jpg',
|
|
15
|
+
'https://example.com/image3.jpg',
|
|
16
|
+
'https://example.com/image4.jpg',
|
|
17
|
+
'https://example.com/image5.jpg'
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
it('renders thumbnails when images are provided', () => {
|
|
21
|
+
const { container } = wrap(<ThumbnailStrip images={sampleImages} />)
|
|
22
|
+
|
|
23
|
+
const wrapper = container.firstChild
|
|
24
|
+
expect(wrapper).toBeTruthy()
|
|
25
|
+
expect(wrapper.children).toHaveLength(4)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('renders correct number of thumbnails based on maxImages prop', () => {
|
|
29
|
+
const { container } = wrap(<ThumbnailStrip images={sampleImages} maxImages={3} />)
|
|
30
|
+
|
|
31
|
+
const wrapper = container.firstChild
|
|
32
|
+
expect(wrapper).toBeTruthy()
|
|
33
|
+
expect(wrapper.children).toHaveLength(3)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('returns null when images array is empty', () => {
|
|
37
|
+
const { container } = wrap(<ThumbnailStrip images={[]} />)
|
|
38
|
+
expect(container.firstChild).toBeNull()
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('returns null when images is null', () => {
|
|
42
|
+
const { container } = wrap(<ThumbnailStrip images={null} />)
|
|
43
|
+
expect(container.firstChild).toBeNull()
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('returns null when images is undefined', () => {
|
|
47
|
+
const { container } = wrap(<ThumbnailStrip />)
|
|
48
|
+
expect(container.firstChild).toBeNull()
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('renders fewer thumbnails when fewer images are provided than maxImages', () => {
|
|
52
|
+
const twoImages = ['https://example.com/image1.jpg', 'https://example.com/image2.jpg']
|
|
53
|
+
const { container } = wrap(<ThumbnailStrip images={twoImages} maxImages={4} />)
|
|
54
|
+
|
|
55
|
+
const wrapper = container.firstChild
|
|
56
|
+
expect(wrapper).toBeTruthy()
|
|
57
|
+
expect(wrapper.children).toHaveLength(2)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('accepts custom size prop', () => {
|
|
61
|
+
const { container } = wrap(<ThumbnailStrip images={sampleImages} size={50} />)
|
|
62
|
+
|
|
63
|
+
const wrapper = container.firstChild
|
|
64
|
+
expect(wrapper).toBeTruthy()
|
|
65
|
+
expect(wrapper.children).toHaveLength(4)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('omits background when an entry is not a non-empty string', () => {
|
|
69
|
+
const mixed = ['https://example.com/a.jpg', '', null]
|
|
70
|
+
const { container } = wrap(<ThumbnailStrip images={mixed} maxImages={4} />)
|
|
71
|
+
expect(container.firstChild?.children?.length).toBe(3)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('matches snapshot with default props', () => {
|
|
75
|
+
const { asFragment } = wrap(<ThumbnailStrip images={sampleImages} />)
|
|
76
|
+
expect(asFragment()).toMatchSnapshot()
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('matches snapshot with custom maxImages and size', () => {
|
|
80
|
+
const { asFragment } = wrap(<ThumbnailStrip images={sampleImages} maxImages={2} size={48} />)
|
|
81
|
+
expect(asFragment()).toMatchSnapshot()
|
|
82
|
+
})
|
|
83
|
+
})
|