Skip to content

Commit 0acf2ca

Browse files
authored
fix: ImagePicker load error status, add demo of upload status for Uploader (#364)
1 parent 3653ecc commit 0acf2ca

File tree

5 files changed

+376
-4
lines changed

5 files changed

+376
-4
lines changed

packages/arcodesign/components/image-picker/__test__/index.spec.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,45 @@ const IMG_DATA =
2323
const mockImgFile = new File([IMG_DATA], 'img.png', { type: 'image/png' });
2424

2525
describe('ImagePicker', () => {
26+
let onerrorRef;
27+
let onloadRef;
28+
const originalImageOnload = Object.getOwnPropertyDescriptor(global.Image.prototype, 'onload');
29+
const originalImageOnerror = Object.getOwnPropertyDescriptor(global.Image.prototype, 'onerror');
30+
31+
beforeAll(() => {
32+
Object.defineProperty(global.Image.prototype, 'onload', {
33+
get() {
34+
return this._onload;
35+
},
36+
set(onload) {
37+
onloadRef = onload;
38+
this._onload = onload;
39+
},
40+
});
41+
Object.defineProperty(global.Image.prototype, 'onerror', {
42+
get() {
43+
return this._onerror;
44+
},
45+
set(onerror) {
46+
onerrorRef = onerror;
47+
this._onerror = onerror;
48+
},
49+
});
50+
});
51+
52+
afterAll(() => {
53+
if (originalImageOnload) {
54+
Object.defineProperty(global.Image.prototype, 'onload', originalImageOnload);
55+
} else {
56+
delete global.Image.prototype.onload;
57+
}
58+
if (originalImageOnerror) {
59+
Object.defineProperty(global.Image.prototype, 'onerror', originalImageOnerror);
60+
} else {
61+
delete global.Image.prototype.onerror;
62+
}
63+
});
64+
2665
beforeEach(() => {
2766
jest.useFakeTimers();
2867
jest.spyOn(global, 'FileReader').mockImplementation(function () {
@@ -236,4 +275,44 @@ describe('ImagePicker', () => {
236275
expect(handleLimitExceed).toBeCalledTimes(1);
237276
});
238277
});
278+
it('upload success but load image failed', async () => {
279+
const mockUpload = async () => {
280+
await new Promise(resolve => setTimeout(resolve, 100));
281+
return {
282+
url: 'http://error.jpg',
283+
};
284+
};
285+
function App() {
286+
const [images, setImages] = React.useState([]);
287+
return <ImagePicker images={images} onChange={setImages} upload={mockUpload} />;
288+
}
289+
const { container } = render(<App />);
290+
const selector = container.querySelector(`.${prefix}-add`);
291+
await userEvent.click(selector);
292+
const input = container.querySelector('input');
293+
await userEvent.upload(input, mockImgFile);
294+
const reader = FileReader.mock.instances[0];
295+
reader.onload({ target: { result: 'foo' } });
296+
await waitFor(() => {
297+
expect(container.querySelectorAll('.image-loading-container').length).toBeGreaterThan(0);
298+
});
299+
300+
// 等待第一次加载的 Image 实例创建
301+
await waitFor(() => {
302+
expect(onerrorRef).toBeDefined();
303+
});
304+
const firstOnerrorRef = onerrorRef;
305+
306+
// 等待第二次加载(mockUpload 完成后,src 更新)
307+
await waitFor(() => {
308+
expect(onerrorRef).not.toBe(firstOnerrorRef);
309+
});
310+
311+
act(() => {
312+
onerrorRef && onerrorRef();
313+
});
314+
await waitFor(() => {
315+
expect(container.querySelector('.image-error-container')).toBeInTheDocument();
316+
});
317+
});
239318
});

packages/arcodesign/components/uploader/__test__/__snapshots__/index.spec.js.snap

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1251,3 +1251,247 @@ exports[`uploader demo test uploader demo: index.md renders correctly 1`] = `
12511251
</div>
12521252
</DocumentFragment>
12531253
`;
1254+
1255+
exports[`uploader demo test uploader demo: upload.md renders correctly 1`] = `
1256+
<DocumentFragment>
1257+
<div>
1258+
<div
1259+
class="demo-space"
1260+
/>
1261+
<div
1262+
class="arco-uploader"
1263+
>
1264+
<div
1265+
class="arco-uploader-container"
1266+
>
1267+
<div
1268+
class="arco-uploader-add"
1269+
>
1270+
<input
1271+
type="file"
1272+
/>
1273+
<button
1274+
class="arco-button arco-button-type-primary type-primary arco-button-size-medium arco-button-size-medium-is-semi size-medium arco-uploader-add-button is-semi pc"
1275+
type="button"
1276+
>
1277+
<div
1278+
class="arco-button-icon btn-icon"
1279+
>
1280+
<svg
1281+
class="arco-icon arco-icon-upload "
1282+
fill="currentColor"
1283+
height="1em"
1284+
viewBox="0 0 14 14"
1285+
width="1em"
1286+
xmlns="http://www.w3.org/2000/svg"
1287+
>
1288+
<path
1289+
clip-rule="evenodd"
1290+
d="M7,1.5l3.1,3.1L9.2,5.4L7.6,3.7v5.9H6.4V3.7L4.8,5.4L3.9,4.6L7,1.5z M2.9,11.4v-1.2H1.8v2.3h10.5 v-2.3h-1.2v1.2H2.9z"
1291+
fill-rule="evenodd"
1292+
/>
1293+
</svg>
1294+
</div>
1295+
<div
1296+
class="arco-button-text arco-button-text-pc btn-text arco-button-text-has-icon has-icon"
1297+
>
1298+
上传
1299+
</div>
1300+
</button>
1301+
</div>
1302+
<div
1303+
class="arco-uploader-list"
1304+
>
1305+
<div
1306+
class="arco-uploader-list-item"
1307+
>
1308+
<div
1309+
class="arco-uploader-list-item-container"
1310+
>
1311+
<div
1312+
class="arco-uploader-list-item-wrapper"
1313+
>
1314+
<div
1315+
class="arco-uploader-list-item-file"
1316+
>
1317+
<svg
1318+
class="arco-icon arco-icon-file arco-uploader-list-item-file-icon"
1319+
fill="currentColor"
1320+
height="1em"
1321+
viewBox="0 0 16 16"
1322+
width="1em"
1323+
xmlns="http://www.w3.org/2000/svg"
1324+
>
1325+
<path
1326+
clip-rule="evenodd"
1327+
d="M2.3,2.7c0-0.7,0.6-1.3,1.3-1.3h7.3l2.7,2.7v9.3c0,0.7-0.6,1.3-1.3,1.3H3.7c-0.7,0-1.3-0.6-1.3-1.3 V2.7z M10.4,2.7H3.7v10.7h8.7V4.6L10.4,2.7z M10.7,7.7H5.3V6.3h5.3V7.7z M8.7,10.3H5.3V9h3.3V10.3z"
1328+
fill-rule="evenodd"
1329+
/>
1330+
</svg>
1331+
</div>
1332+
<div
1333+
class="arco-uploader-list-item-text"
1334+
>
1335+
employeelist.doc
1336+
</div>
1337+
</div>
1338+
<div
1339+
class="arco-uploader-list-item-status"
1340+
>
1341+
<div
1342+
class="arco-uploader-list-item-loaded"
1343+
>
1344+
<svg
1345+
class="arco-icon arco-icon-check "
1346+
fill="currentColor"
1347+
height="1em"
1348+
viewBox="0 0 20 20"
1349+
width="1em"
1350+
xmlns="http://www.w3.org/2000/svg"
1351+
>
1352+
<path
1353+
clip-rule="evenodd"
1354+
d="M16.702 4.47a.5.5 0 00-.705.06L8.33 13.596 3.82 9.724a.5.5 0 00-.705.054l-.652.758a.5.5 0 00.054.706L7.361 15.4a.5.5 0 00.054.053l.526.445.22.188a.5.5 0 00.722-.047l8.641-10.218a.5.5 0 00-.059-.705l-.763-.645z"
1355+
fill-rule="evenodd"
1356+
/>
1357+
</svg>
1358+
</div>
1359+
</div>
1360+
</div>
1361+
<div
1362+
class="arco-uploader-list-item-delete"
1363+
>
1364+
<svg
1365+
class="arco-icon arco-icon-delete arco-uploader-list-item-delete-icon"
1366+
height="1em"
1367+
viewBox="0 0 1024 1024"
1368+
width="1em"
1369+
xmlns="http://www.w3.org/2000/svg"
1370+
>
1371+
<path
1372+
d="M640 85.333A42.667 42.667 0 01682.667 128l-.022 42.645 228.694.022c9.493 0 12.928.981 16.426 2.858a19.41 19.41 0 018.043 8.064c1.877 3.478 2.859 6.912 2.859 16.427v30.635c0 9.514-.982 12.949-2.859 16.426a19.392 19.392 0 01-8.064 8.064c-3.477 1.878-6.912 2.859-16.427 2.859l-57.984-.021V896a42.667 42.667 0 01-42.666 42.667H213.333A42.667 42.667 0 01170.667 896l-.022-640.021-57.962.021c-9.515 0-12.95-.981-16.427-2.859a19.392 19.392 0 01-8.064-8.064c-1.877-3.477-2.859-6.912-2.859-16.426v-30.635c0-9.515.982-12.95 2.859-16.427a19.392 19.392 0 018.064-8.064c3.477-1.877 6.912-2.858 16.427-2.858l228.629-.022.021-42.645A42.667 42.667 0 01384 85.333h256zM768 256H256v597.333h512V256zM448 384a21.333 21.333 0 0121.333 21.333V704A21.333 21.333 0 01448 725.333h-42.667A21.333 21.333 0 01384 704V405.333A21.333 21.333 0 01405.333 384H448zm170.667 0A21.333 21.333 0 01640 405.333V704a21.333 21.333 0 01-21.333 21.333H576A21.333 21.333 0 01554.667 704V405.333A21.333 21.333 0 01576 384h42.667z"
1373+
fill="currentColor"
1374+
/>
1375+
</svg>
1376+
</div>
1377+
</div>
1378+
</div>
1379+
</div>
1380+
</div>
1381+
<div
1382+
class="demo-space"
1383+
/>
1384+
<div
1385+
class="arco-uploader"
1386+
>
1387+
<div
1388+
class="arco-uploader-container"
1389+
>
1390+
<div
1391+
class="arco-uploader-add"
1392+
>
1393+
<input
1394+
type="file"
1395+
/>
1396+
<button
1397+
class="arco-button arco-button-type-primary type-primary arco-button-size-medium arco-button-size-medium-is-semi size-medium arco-uploader-add-button is-semi pc"
1398+
type="button"
1399+
>
1400+
<div
1401+
class="arco-button-icon btn-icon"
1402+
>
1403+
<svg
1404+
class="arco-icon arco-icon-upload "
1405+
fill="currentColor"
1406+
height="1em"
1407+
viewBox="0 0 14 14"
1408+
width="1em"
1409+
xmlns="http://www.w3.org/2000/svg"
1410+
>
1411+
<path
1412+
clip-rule="evenodd"
1413+
d="M7,1.5l3.1,3.1L9.2,5.4L7.6,3.7v5.9H6.4V3.7L4.8,5.4L3.9,4.6L7,1.5z M2.9,11.4v-1.2H1.8v2.3h10.5 v-2.3h-1.2v1.2H2.9z"
1414+
fill-rule="evenodd"
1415+
/>
1416+
</svg>
1417+
</div>
1418+
<div
1419+
class="arco-button-text arco-button-text-pc btn-text arco-button-text-has-icon has-icon"
1420+
>
1421+
上传
1422+
</div>
1423+
</button>
1424+
</div>
1425+
<div
1426+
class="arco-uploader-list"
1427+
>
1428+
<div
1429+
class="arco-uploader-list-item"
1430+
>
1431+
<div
1432+
class="arco-uploader-list-item-container"
1433+
>
1434+
<div
1435+
class="arco-uploader-list-item-wrapper"
1436+
>
1437+
<div
1438+
class="arco-uploader-list-item-file"
1439+
>
1440+
<svg
1441+
class="arco-icon arco-icon-file arco-uploader-list-item-file-icon"
1442+
fill="currentColor"
1443+
height="1em"
1444+
viewBox="0 0 16 16"
1445+
width="1em"
1446+
xmlns="http://www.w3.org/2000/svg"
1447+
>
1448+
<path
1449+
clip-rule="evenodd"
1450+
d="M2.3,2.7c0-0.7,0.6-1.3,1.3-1.3h7.3l2.7,2.7v9.3c0,0.7-0.6,1.3-1.3,1.3H3.7c-0.7,0-1.3-0.6-1.3-1.3 V2.7z M10.4,2.7H3.7v10.7h8.7V4.6L10.4,2.7z M10.7,7.7H5.3V6.3h5.3V7.7z M8.7,10.3H5.3V9h3.3V10.3z"
1451+
fill-rule="evenodd"
1452+
/>
1453+
</svg>
1454+
</div>
1455+
<div
1456+
class="arco-uploader-list-item-text arco-uploader-list-item-text-error"
1457+
>
1458+
employeelist.doc
1459+
</div>
1460+
</div>
1461+
<div
1462+
class="arco-uploader-list-item-status"
1463+
>
1464+
<div>
1465+
<div
1466+
class="arco-uploader-list-item-error"
1467+
>
1468+
<span>
1469+
重试
1470+
</span>
1471+
</div>
1472+
</div>
1473+
</div>
1474+
</div>
1475+
<div
1476+
class="arco-uploader-list-item-delete"
1477+
>
1478+
<svg
1479+
class="arco-icon arco-icon-delete arco-uploader-list-item-delete-icon"
1480+
height="1em"
1481+
viewBox="0 0 1024 1024"
1482+
width="1em"
1483+
xmlns="http://www.w3.org/2000/svg"
1484+
>
1485+
<path
1486+
d="M640 85.333A42.667 42.667 0 01682.667 128l-.022 42.645 228.694.022c9.493 0 12.928.981 16.426 2.858a19.41 19.41 0 018.043 8.064c1.877 3.478 2.859 6.912 2.859 16.427v30.635c0 9.514-.982 12.949-2.859 16.426a19.392 19.392 0 01-8.064 8.064c-3.477 1.878-6.912 2.859-16.427 2.859l-57.984-.021V896a42.667 42.667 0 01-42.666 42.667H213.333A42.667 42.667 0 01170.667 896l-.022-640.021-57.962.021c-9.515 0-12.95-.981-16.427-2.859a19.392 19.392 0 01-8.064-8.064c-1.877-3.477-2.859-6.912-2.859-16.426v-30.635c0-9.515.982-12.95 2.859-16.427a19.392 19.392 0 018.064-8.064c3.477-1.877 6.912-2.858 16.427-2.858l228.629-.022.021-42.645A42.667 42.667 0 01384 85.333h256zM768 256H256v597.333h512V256zM448 384a21.333 21.333 0 0121.333 21.333V704A21.333 21.333 0 01448 725.333h-42.667A21.333 21.333 0 01384 704V405.333A21.333 21.333 0 01405.333 384H448zm170.667 0A21.333 21.333 0 01640 405.333V704a21.333 21.333 0 01-21.333 21.333H576A21.333 21.333 0 01554.667 704V405.333A21.333 21.333 0 01576 384h42.667z"
1487+
fill="currentColor"
1488+
/>
1489+
</svg>
1490+
</div>
1491+
</div>
1492+
</div>
1493+
</div>
1494+
</div>
1495+
</div>
1496+
</DocumentFragment>
1497+
`;
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
## 上传状态 @en{Upload}
2+
3+
#### 7
4+
5+
```js
6+
import { Uploader } from '@arco-design/mobile-react';
7+
8+
export const sleep = time => new Promise(resolve => setTimeout(resolve, time));
9+
10+
const mimeType = 'text/plain';
11+
const blob = new Blob([''], { type: mimeType });
12+
const file = new File([blob], 'employeelist.doc', {
13+
type: mimeType,
14+
});
15+
16+
export async function mockUpload({ file }) {
17+
await sleep(1000);
18+
return {
19+
url: URL.createObjectURL(file),
20+
};
21+
}
22+
23+
export async function mockUploadFail() {
24+
await sleep(3000);
25+
throw new Error('Upload failed');
26+
}
27+
28+
export default function UploaderDemo() {
29+
const [files, setFiles] = React.useState([{ file, status: 'loaded' },]);
30+
const [files1, setFiles1] = React.useState([{ file, status: 'error' },]);
31+
return (
32+
<div>
33+
<div className="demo-space" />
34+
<Uploader files={files} onChange={setFiles} upload={mockUpload} />
35+
<div className="demo-space" />
36+
<Uploader files={files1} onChange={setFiles1} upload={mockUploadFail} />
37+
</div>
38+
);
39+
}
40+
```
41+
42+
```less
43+
.demo-space {
44+
font-size: 14px;
45+
line-height: 1;
46+
margin: 8px 0;
47+
}
48+
```

0 commit comments

Comments
 (0)