@jetshop/ui 6.3.8-alpha.20 → 6.3.8-alpha.25
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.
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { ChannelProvider } from '@jetshop/core/components/ChannelContext';
|
|
2
|
+
import t from '@jetshop/intl';
|
|
3
|
+
import testImage from '@jetshop/ui/test-utils/fixtures/test-image.png';
|
|
4
|
+
import { fireEvent, queryByText, render } from '@testing-library/react';
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { useInView } from 'react-intersection-observer';
|
|
7
|
+
import Image from './FlightImage';
|
|
8
|
+
import { ChannelHandler } from '@jetshop/core/ChannelHandler/ChannelHandler';
|
|
9
|
+
import { selectedChannel, channels } from '@jetshop/core/test-utils/variables';
|
|
10
|
+
import { ConfigProvider } from '@jetshop/core/components/ConfigProvider';
|
|
11
|
+
import mockShopConfig from '@jetshop/core/test-utils/mockShopConfig';
|
|
12
|
+
|
|
13
|
+
// Mock intersection observer so we can always return true/false
|
|
14
|
+
jest.mock('react-intersection-observer');
|
|
15
|
+
// Suppress warnings from translations
|
|
16
|
+
jest.mock('@jetshop/intl');
|
|
17
|
+
t.mockImplementation((t) => t);
|
|
18
|
+
|
|
19
|
+
// browser mocks
|
|
20
|
+
global.IntersectionObserver = jest.fn(function () {
|
|
21
|
+
this.observe = jest.fn();
|
|
22
|
+
this.unobserve = jest.fn();
|
|
23
|
+
this.disconnect = jest.fn();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const transparent =
|
|
27
|
+
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
|
|
28
|
+
|
|
29
|
+
global.__IN_SERVER__ = false;
|
|
30
|
+
|
|
31
|
+
describe('Hooks image component', () => {
|
|
32
|
+
describe('on initial load', () => {
|
|
33
|
+
beforeAll(() => {
|
|
34
|
+
useInView.mockImplementation(() => [null, false]);
|
|
35
|
+
});
|
|
36
|
+
it('renders a lqip version of the image by default', () => {
|
|
37
|
+
const { placeholderImage } = setup();
|
|
38
|
+
|
|
39
|
+
const src = placeholderImage.getAttribute('src');
|
|
40
|
+
|
|
41
|
+
expect(src).toEqual(expect.stringContaining('sigma=2.5'));
|
|
42
|
+
expect(src).toEqual(expect.stringContaining('minampl=0.5'));
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('sets the lqip to the smallest of the calculated sizes', () => {
|
|
46
|
+
const { placeholderImage } = setup({ sizes: [0.5, 0.85, 1] });
|
|
47
|
+
// Should be 160
|
|
48
|
+
// The smallest breakpoint is 20rem, which is 360px.
|
|
49
|
+
// 0.5 x 360 = 160
|
|
50
|
+
|
|
51
|
+
const src = placeholderImage.getAttribute('src');
|
|
52
|
+
|
|
53
|
+
expect(src).toEqual(expect.stringContaining('width=160'));
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('does not render an lqip if critical is true', () => {
|
|
57
|
+
const { placeholderImage } = setup({ critical: true });
|
|
58
|
+
|
|
59
|
+
const src = placeholderImage.getAttribute('src');
|
|
60
|
+
|
|
61
|
+
// Sets the src to transaprent data image instead
|
|
62
|
+
expect(src).toEqual(expect.stringContaining('data:image'));
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('accepts sizes as pixel values', () => {
|
|
66
|
+
const { placeholderImage } = setup({ sizes: [123] });
|
|
67
|
+
|
|
68
|
+
const src = placeholderImage.getAttribute('src');
|
|
69
|
+
|
|
70
|
+
expect(src).toEqual(expect.stringContaining('width=123'));
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('sets opacity to 0.6 while image has not loaded', () => {
|
|
74
|
+
const { placeholderImage } = setup();
|
|
75
|
+
|
|
76
|
+
expect(placeholderImage).toHaveStyle('opacity: 0.6');
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('sets the sizing method', () => {
|
|
81
|
+
it('to thumbnail if no aspect ratio is provided', () => {
|
|
82
|
+
const { placeholderImage } = setup();
|
|
83
|
+
});
|
|
84
|
+
it('to fit if an aspect ratio is provided', () => {
|
|
85
|
+
const { placeholderImage } = setup({ aspect: '1:1' });
|
|
86
|
+
|
|
87
|
+
expect(placeholderImage.getAttribute('src')).toEqual(
|
|
88
|
+
expect.stringContaining('&height=320')
|
|
89
|
+
);
|
|
90
|
+
expect(placeholderImage.getAttribute('src')).toEqual(
|
|
91
|
+
expect.stringContaining('&width=320')
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
it('to crop if crop prop is true', () => {
|
|
95
|
+
const { placeholderImage } = setup({ aspect: '1:1', crop: true });
|
|
96
|
+
|
|
97
|
+
expect(placeholderImage.getAttribute('src')).toEqual(
|
|
98
|
+
expect.stringContaining('&method=crop')
|
|
99
|
+
);
|
|
100
|
+
});
|
|
101
|
+
it('warns if gravity is provided without crop', () => {
|
|
102
|
+
console.warn = jest.fn();
|
|
103
|
+
setup({ aspect: '1:1', crop: false, gravity: 'north' });
|
|
104
|
+
expect(console.warn).toHaveBeenCalled();
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('when image is in view', () => {
|
|
109
|
+
beforeAll(() => {
|
|
110
|
+
useInView.mockImplementation(() => [null, true]);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('renders full image', () => {
|
|
114
|
+
const { image } = setup({
|
|
115
|
+
imageSrc: testImage
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
expect(image).toHaveAttribute('src', 'test-image.png');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('applies the given alt as an aria-label', () => {
|
|
122
|
+
const { image } = setup({
|
|
123
|
+
alt: 'test alt'
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
expect(image).toHaveAttribute('alt', 'test alt');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('defaults aspect to 1:1', () => {
|
|
130
|
+
const { imageContainer } = setup();
|
|
131
|
+
|
|
132
|
+
expect(imageContainer).toHaveStyle('padding-bottom : 100%');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('sets aspect ratio using padding if aspect is passed', () => {
|
|
136
|
+
const { imageContainer } = setup({ aspect: '3:2' });
|
|
137
|
+
|
|
138
|
+
expect(imageContainer).toHaveStyle('padding-bottom : 66.6667%');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('defaults to background-size contain', () => {
|
|
142
|
+
const { image } = setup();
|
|
143
|
+
|
|
144
|
+
expect(image).toHaveStyle('object-fit : contain');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('sets background-size to cover if `cover` is passed', () => {
|
|
148
|
+
const { image } = setup({ cover: true });
|
|
149
|
+
|
|
150
|
+
expect(image).toHaveStyle('object-fit : cover');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('sets up image to fill available space if `fillAvailableSpace` is passed', () => {
|
|
154
|
+
const { image } = setup({ fillAvailableSpace: true });
|
|
155
|
+
|
|
156
|
+
expect(image).toHaveStyle('object-fit : cover');
|
|
157
|
+
expect(image).toHaveStyle('padding-top : 0');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('sets opacity to 1 when image loads', () => {
|
|
161
|
+
const { image } = setup();
|
|
162
|
+
fireEvent.load(image);
|
|
163
|
+
|
|
164
|
+
expect(image).toHaveStyle('opacity: 1');
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('fades lqip when image loads', () => {
|
|
168
|
+
const { image, placeholderImage } = setup();
|
|
169
|
+
fireEvent.load(image);
|
|
170
|
+
|
|
171
|
+
expect(placeholderImage).toHaveStyle('opacity: 0');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('sets quality to quality prop', () => {
|
|
175
|
+
const { image } = setup({ quality: 1 });
|
|
176
|
+
|
|
177
|
+
expect(image.getAttribute('srcSet')).toEqual(
|
|
178
|
+
expect.stringContaining('&quality=1')
|
|
179
|
+
);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe('when there is an image error', () => {
|
|
184
|
+
beforeAll(() => {
|
|
185
|
+
useInView.mockImplementation(() => [null, true]);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('renders default error', () => {
|
|
189
|
+
const { image, getByText } = setup();
|
|
190
|
+
fireEvent.error(image);
|
|
191
|
+
|
|
192
|
+
expect(getByText('Image Not Found'));
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('renders error component', () => {
|
|
196
|
+
const { image, getByText, container } = setup({
|
|
197
|
+
error: () => <div>some error found</div>
|
|
198
|
+
});
|
|
199
|
+
fireEvent.error(image);
|
|
200
|
+
|
|
201
|
+
expect(getByText('some error found'));
|
|
202
|
+
expect(queryByText(container, 'Image Not Found')).not.toBeInTheDocument();
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
describe('when image url is falsy', () => {
|
|
207
|
+
it('renders a transparent background image when null', () => {
|
|
208
|
+
const { image } = setup({ falsySrc: true, imageSrc: null });
|
|
209
|
+
|
|
210
|
+
expect(image).toHaveAttribute(`src`, transparent);
|
|
211
|
+
expect(image).toHaveStyle('opacity : 1');
|
|
212
|
+
});
|
|
213
|
+
it('renders a transparent background image when undefined', () => {
|
|
214
|
+
const { image } = setup({ falsySrc: true, imageSrc: undefined });
|
|
215
|
+
|
|
216
|
+
expect(image).toHaveAttribute(`src`, transparent);
|
|
217
|
+
expect(image).toHaveStyle('opacity: 1');
|
|
218
|
+
});
|
|
219
|
+
it('renders nothing and warns if false', () => {
|
|
220
|
+
console.warn = jest.fn();
|
|
221
|
+
const { image } = setup({ falsySrc: true, imageSrc: false });
|
|
222
|
+
|
|
223
|
+
expect(image).not.toBeInTheDocument();
|
|
224
|
+
expect(console.warn).toHaveBeenCalled();
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('renders a webp and jpeg source', () => {
|
|
229
|
+
const { imageContainer } = setup();
|
|
230
|
+
|
|
231
|
+
const sources = imageContainer.querySelectorAll('source');
|
|
232
|
+
expect(sources.length).toBe(2);
|
|
233
|
+
expect(sources[0]).toHaveAttribute('type', 'image/webp');
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
function setup({
|
|
238
|
+
imageSrc,
|
|
239
|
+
falsySrc = false,
|
|
240
|
+
error,
|
|
241
|
+
alt,
|
|
242
|
+
sizes,
|
|
243
|
+
aspect,
|
|
244
|
+
cover,
|
|
245
|
+
...rest
|
|
246
|
+
} = {}) {
|
|
247
|
+
const channelHandler = new ChannelHandler({
|
|
248
|
+
selectedChannel,
|
|
249
|
+
channels
|
|
250
|
+
});
|
|
251
|
+
const utils = render(
|
|
252
|
+
<ChannelProvider channelHandler={channelHandler}>
|
|
253
|
+
<ConfigProvider config={mockShopConfig}>
|
|
254
|
+
<Image
|
|
255
|
+
src={falsySrc ? imageSrc : imageSrc || 'default.png'}
|
|
256
|
+
error={error}
|
|
257
|
+
alt={alt}
|
|
258
|
+
sizes={sizes}
|
|
259
|
+
aspect={aspect}
|
|
260
|
+
cover={cover}
|
|
261
|
+
{...rest}
|
|
262
|
+
/>
|
|
263
|
+
</ConfigProvider>
|
|
264
|
+
</ChannelProvider>
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
...utils,
|
|
269
|
+
image: utils.container.querySelector('[data-flight-image] img'),
|
|
270
|
+
placeholderImage: utils.container.querySelector(
|
|
271
|
+
'[data-flight-image-placeholder] img'
|
|
272
|
+
),
|
|
273
|
+
imageContainer: utils.container.querySelector(
|
|
274
|
+
'[data-flight-image-container]'
|
|
275
|
+
)
|
|
276
|
+
};
|
|
277
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jetshop/ui",
|
|
3
|
-
"version": "6.3.8-alpha.
|
|
3
|
+
"version": "6.3.8-alpha.25+54a210697",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"files": [
|
|
6
6
|
"**/*.js",
|
|
@@ -60,5 +60,5 @@
|
|
|
60
60
|
"react": "^18",
|
|
61
61
|
"react-dom": "^18"
|
|
62
62
|
},
|
|
63
|
-
"gitHead": "
|
|
63
|
+
"gitHead": "54a2106975949d242f2ced3121792b836a463c3c"
|
|
64
64
|
}
|