1import { Asset } from 'expo-asset';
2import * as FileSystem from 'expo-file-system';
3import * as ImageManipulator from 'expo-image-manipulator';
4import { Platform } from 'react-native';
5
6export const name = 'ImageManipulator';
7
8export async function test(t) {
9  t.describe('ImageManipulator', async () => {
10    let image;
11
12    t.beforeAll(async () => {
13      image = Asset.fromModule(require('../assets/example_image_1.jpg'));
14      await image.downloadAsync();
15    });
16
17    t.describe('manipulateAsync()', async () => {
18      t.it('returns valid image', async () => {
19        const result = await ImageManipulator.manipulateAsync(image.localUri, [
20          { resize: { width: 100, height: 100 } },
21        ]);
22        t.expect(result).toBeDefined();
23        t.expect(typeof result.uri).toBe('string');
24        t.expect(typeof result.width).toBe('number');
25        t.expect(typeof result.height).toBe('number');
26      });
27
28      t.it('returns valid image from base64 data URL', async () => {
29        // 1x1 red image
30        const result = await ImageManipulator.manipulateAsync(
31          'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg==',
32          [{ resize: { width: 100, height: 100 } }]
33        );
34        t.expect(result).toBeDefined();
35        t.expect(typeof result.uri).toBe('string');
36        t.expect(result.width).toBe(100);
37        t.expect(result.height).toBe(100);
38      });
39
40      t.it('saves with default format', async () => {
41        const result = await ImageManipulator.manipulateAsync(image.localUri, [
42          { resize: { width: 100, height: 100 } },
43        ]);
44
45        if (Platform.OS === 'web') {
46          t.expect(result.uri.startsWith('data:image/jpeg;base64,')).toBe(true);
47        } else {
48          t.expect(result.uri.endsWith('.jpg')).toBe(true);
49        }
50      });
51
52      t.it('saves as JPEG', async () => {
53        const result = await ImageManipulator.manipulateAsync(
54          image.localUri,
55          [{ resize: { width: 100, height: 100 } }],
56          {
57            format: ImageManipulator.SaveFormat.JPEG,
58          }
59        );
60
61        if (Platform.OS === 'web') {
62          t.expect(result.uri.startsWith('data:image/jpeg;base64,')).toBe(true);
63        } else {
64          t.expect(result.uri.endsWith('.jpg')).toBe(true);
65        }
66      });
67
68      t.it('saves as PNG', async () => {
69        const result = await ImageManipulator.manipulateAsync(
70          image.localUri,
71          [{ resize: { width: 100, height: 100 } }],
72          {
73            format: ImageManipulator.SaveFormat.PNG,
74          }
75        );
76
77        if (Platform.OS === 'web') {
78          t.expect(result.uri.startsWith('data:image/png;base64,')).toBe(true);
79        } else {
80          t.expect(result.uri.endsWith('.png')).toBe(true);
81        }
82      });
83
84      t.it('provides Base64 with no header or newline terminator', async () => {
85        const result = await ImageManipulator.manipulateAsync(
86          image.localUri,
87          [{ resize: { width: 100, height: 100 } }],
88          {
89            base64: true,
90          }
91        );
92
93        t.expect(typeof result.base64).toBe('string');
94        t.expect(result.base64).not.toContain('\n');
95        t.expect(result.base64).not.toContain('\r');
96        t.expect(result.base64.startsWith('data:image/jpeg;base64,')).toBe(false);
97      });
98
99      t.it('performs compression', async () => {
100        const result = await ImageManipulator.manipulateAsync(
101          image.localUri,
102          [{ flip: ImageManipulator.FlipType.Vertical }],
103          {
104            compress: 0.0,
105          }
106        );
107
108        if (Platform.OS === 'web') {
109          const imageInfo = await fetch(image.localUri).then((a) => a.blob());
110          const resultInfo = await fetch(result.uri).then((a) => a.blob());
111
112          t.expect(imageInfo.size).toBeGreaterThan(resultInfo.size);
113        } else {
114          const imageInfo = await FileSystem.getInfoAsync(image.localUri);
115          const resultInfo = await FileSystem.getInfoAsync(result.uri);
116
117          t.expect(imageInfo.size).toBeGreaterThan(resultInfo.size);
118        }
119      });
120
121      t.it('rotates images', async () => {
122        const result = await ImageManipulator.manipulateAsync(image.localUri, [{ rotate: 45 }]);
123        t.expect(result.width).toBeGreaterThan(image.width);
124      });
125
126      t.it('flips horizontally', async () => {
127        const result = await ImageManipulator.manipulateAsync(image.localUri, [
128          { flip: ImageManipulator.FlipType.Horizontal },
129        ]);
130        t.expect(result.width).toBe(image.width);
131        t.expect(result.height).toBe(image.height);
132      });
133
134      t.it('flips vertically', async () => {
135        const result = await ImageManipulator.manipulateAsync(image.localUri, [
136          { flip: ImageManipulator.FlipType.Vertical },
137        ]);
138        t.expect(result.width).toBe(image.width);
139        t.expect(result.height).toBe(image.height);
140      });
141
142      t.it('resizes image', async () => {
143        const result = await ImageManipulator.manipulateAsync(image.localUri, [
144          { resize: { width: 100, height: 100 } },
145        ]);
146        t.expect(result.height).toBe(100);
147        t.expect(result.width).toBe(100);
148      });
149
150      t.it('crops image', async () => {
151        const result = await ImageManipulator.manipulateAsync(image.localUri, [
152          { crop: { originX: 20, originY: 20, width: 100, height: 100 } },
153        ]);
154        t.expect(result.height).toBe(100);
155        t.expect(result.width).toBe(100);
156      });
157
158      t.it('performs multiple transformations', async () => {
159        const result = await ImageManipulator.manipulateAsync(image.localUri, [
160          { resize: { width: 200, height: 200 } },
161          { flip: ImageManipulator.FlipType.Vertical },
162          { rotate: 45 },
163          { crop: { originX: 20, originY: 20, width: 100, height: 100 } },
164        ]);
165        t.expect(result.height).toBe(100);
166        t.expect(result.width).toBe(100);
167      });
168    });
169  });
170}
171