react-native-libyuv-resizer 0.2.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.
Files changed (50) hide show
  1. package/LICENSE +20 -0
  2. package/LibyuvResizer.podspec +20 -0
  3. package/README.md +188 -0
  4. package/android/CMakeLists.txt +30 -0
  5. package/android/build.gradle +100 -0
  6. package/android/src/androidTest/java/com/libyuvresizer/ExifCopierTest.kt +131 -0
  7. package/android/src/androidTest/java/com/libyuvresizer/FakePromise.kt +71 -0
  8. package/android/src/androidTest/java/com/libyuvresizer/FakeReactContext.kt +55 -0
  9. package/android/src/androidTest/java/com/libyuvresizer/LibyuvResizerModuleErrorTest.kt +135 -0
  10. package/android/src/androidTest/java/com/libyuvresizer/LibyuvResizerModuleExifTest.kt +140 -0
  11. package/android/src/androidTest/java/com/libyuvresizer/LibyuvResizerModuleFilterModeTest.kt +85 -0
  12. package/android/src/androidTest/java/com/libyuvresizer/LibyuvResizerModuleFormatTest.kt +146 -0
  13. package/android/src/androidTest/java/com/libyuvresizer/LibyuvResizerModuleIntegrationTest.kt +157 -0
  14. package/android/src/androidTest/java/com/libyuvresizer/LibyuvResizerModuleOutputPathTest.kt +96 -0
  15. package/android/src/androidTest/java/com/libyuvresizer/LibyuvResizerModuleRotationTest.kt +120 -0
  16. package/android/src/androidTest/java/com/libyuvresizer/TestFixtures.kt +48 -0
  17. package/android/src/main/AndroidManifest.xml +2 -0
  18. package/android/src/main/cpp/LibyuvResizerModule.cpp +137 -0
  19. package/android/src/main/java/com/libyuvresizer/DimensionCalculator.kt +52 -0
  20. package/android/src/main/java/com/libyuvresizer/ExifCopier.kt +133 -0
  21. package/android/src/main/java/com/libyuvresizer/LibyuvResizerModule.kt +179 -0
  22. package/android/src/main/java/com/libyuvresizer/LibyuvResizerPackage.kt +30 -0
  23. package/android/src/main/java/com/libyuvresizer/ResizeValidator.kt +71 -0
  24. package/android/src/test/java/com/libyuvresizer/DimensionCalculatorTest.kt +181 -0
  25. package/android/src/test/java/com/libyuvresizer/ResizeValidatorTest.kt +203 -0
  26. package/ios/LibyuvResizer.h +6 -0
  27. package/ios/LibyuvResizer.mm +31 -0
  28. package/lib/module/NativeLibyuvResizer.js +28 -0
  29. package/lib/module/NativeLibyuvResizer.js.map +1 -0
  30. package/lib/module/index.js +20 -0
  31. package/lib/module/index.js.map +1 -0
  32. package/lib/module/package.json +1 -0
  33. package/lib/module/resizer.js +15 -0
  34. package/lib/module/resizer.js.map +1 -0
  35. package/lib/module/resizer.native.js +110 -0
  36. package/lib/module/resizer.native.js.map +1 -0
  37. package/lib/typescript/package.json +1 -0
  38. package/lib/typescript/src/NativeLibyuvResizer.d.ts +52 -0
  39. package/lib/typescript/src/NativeLibyuvResizer.d.ts.map +1 -0
  40. package/lib/typescript/src/index.d.ts +19 -0
  41. package/lib/typescript/src/index.d.ts.map +1 -0
  42. package/lib/typescript/src/resizer.d.ts +13 -0
  43. package/lib/typescript/src/resizer.d.ts.map +1 -0
  44. package/lib/typescript/src/resizer.native.d.ts +119 -0
  45. package/lib/typescript/src/resizer.native.d.ts.map +1 -0
  46. package/package.json +184 -0
  47. package/src/NativeLibyuvResizer.ts +81 -0
  48. package/src/index.tsx +23 -0
  49. package/src/resizer.native.tsx +175 -0
  50. package/src/resizer.tsx +31 -0
@@ -0,0 +1,135 @@
1
+ package com.libyuvresizer
2
+
3
+ import androidx.test.ext.junit.runners.AndroidJUnit4
4
+ import androidx.test.platform.app.InstrumentationRegistry
5
+ import org.junit.After
6
+ import org.junit.Assert.assertEquals
7
+ import org.junit.Assert.assertFalse
8
+ import org.junit.Assert.assertTrue
9
+ import org.junit.Before
10
+ import org.junit.Test
11
+ import org.junit.runner.RunWith
12
+
13
+ @RunWith(AndroidJUnit4::class)
14
+ class LibyuvResizerModuleErrorTest {
15
+
16
+ private lateinit var module: LibyuvResizerModule
17
+ private lateinit var reactContext: FakeReactContext
18
+ private val createdFiles = mutableListOf<String>()
19
+
20
+ @Before
21
+ fun setUp() {
22
+ val ctx = InstrumentationRegistry.getInstrumentation().targetContext
23
+ reactContext = FakeReactContext(ctx)
24
+ module = LibyuvResizerModule(reactContext)
25
+ }
26
+
27
+ @After
28
+ fun tearDown() {
29
+ createdFiles.forEach { TestFixtures.deleteIfExists(it) }
30
+ createdFiles.clear()
31
+ }
32
+
33
+ private fun resize(
34
+ filePath: String = "/nonexistent/file.jpg",
35
+ targetW: Double = 100.0,
36
+ targetH: Double = 100.0,
37
+ quality: Double = 80.0,
38
+ rotation: Double = 0.0,
39
+ mode: String = "contain",
40
+ outputPath: String = "",
41
+ filterMode: String = "box",
42
+ format: String = "jpeg"
43
+ ): FakePromise {
44
+ val promise = FakePromise()
45
+ module.resize(filePath, targetW, targetH, quality, rotation, mode, outputPath, filterMode, false, format, promise)
46
+ return promise
47
+ }
48
+
49
+ @Test
50
+ fun resize_nonexistentFile_rejectsWithFileNotFound() {
51
+ val promise = resize(filePath = "/nonexistent/path/image.jpg")
52
+
53
+ assertTrue(promise.rejected)
54
+ assertFalse(promise.resolved)
55
+ assertEquals("E_FILE_NOT_FOUND", promise.errorCode)
56
+ }
57
+
58
+ @Test
59
+ fun resize_corruptFile_rejectsWithDecodeFailed() {
60
+ val corrupt = TestFixtures.createCorruptFile(reactContext, "corrupt.jpg")
61
+ createdFiles += corrupt
62
+
63
+ val promise = resize(filePath = corrupt)
64
+
65
+ assertTrue(promise.rejected)
66
+ assertEquals("E_DECODE_FAILED", promise.errorCode)
67
+ }
68
+
69
+ @Test
70
+ fun resize_zeroDimensions_rejectsWithInvalidDims() {
71
+ val src = TestFixtures.createJpeg(reactContext, 100, 100, "dims_test.jpg")
72
+ createdFiles += src
73
+
74
+ val promise = resize(filePath = src, targetW = 0.0, targetH = 100.0)
75
+
76
+ assertTrue(promise.rejected)
77
+ assertEquals("E_INVALID_DIMS", promise.errorCode)
78
+ }
79
+
80
+ @Test
81
+ fun resize_outOfRangeQuality_rejectsWithInvalidQuality() {
82
+ val src = TestFixtures.createJpeg(reactContext, 100, 100, "quality_test.jpg")
83
+ createdFiles += src
84
+
85
+ val promise = resize(filePath = src, quality = 0.0)
86
+
87
+ assertTrue(promise.rejected)
88
+ assertEquals("E_INVALID_QUALITY", promise.errorCode)
89
+ }
90
+
91
+ @Test
92
+ fun resize_invalidMode_rejectsWithInvalidMode() {
93
+ val src = TestFixtures.createJpeg(reactContext, 100, 100, "mode_test.jpg")
94
+ createdFiles += src
95
+
96
+ val promise = resize(filePath = src, mode = "fill")
97
+
98
+ assertTrue(promise.rejected)
99
+ assertEquals("E_INVALID_MODE", promise.errorCode)
100
+ }
101
+
102
+ @Test
103
+ fun resize_invalidFilterMode_rejectsWithInvalidFilterMode() {
104
+ val src = TestFixtures.createJpeg(reactContext, 100, 100, "filter_test.jpg")
105
+ createdFiles += src
106
+
107
+ val promise = resize(filePath = src, filterMode = "lanczos")
108
+
109
+ assertTrue(promise.rejected)
110
+ assertEquals("E_INVALID_FILTER_MODE", promise.errorCode)
111
+ }
112
+
113
+ @Test
114
+ fun resize_invalidRotation_rejectsWithInvalidRotation() {
115
+ val src = TestFixtures.createJpeg(reactContext, 100, 100, "rot_test.jpg")
116
+ createdFiles += src
117
+
118
+ val promise = resize(filePath = src, rotation = 45.0)
119
+
120
+ assertTrue(promise.rejected)
121
+ assertEquals("E_INVALID_ROTATION", promise.errorCode)
122
+ }
123
+
124
+ @Test
125
+ fun resize_outputPathIsFile_rejectsWithInvalidOutputPath() {
126
+ val src = TestFixtures.createJpeg(reactContext, 100, 100, "outpath_test.jpg")
127
+ createdFiles += src
128
+
129
+ // outputPath points to a file, not a directory
130
+ val promise = resize(filePath = src, outputPath = src)
131
+
132
+ assertTrue(promise.rejected)
133
+ assertEquals("E_INVALID_OUTPUT_PATH", promise.errorCode)
134
+ }
135
+ }
@@ -0,0 +1,140 @@
1
+ package com.libyuvresizer
2
+
3
+ import androidx.exifinterface.media.ExifInterface
4
+ import androidx.test.ext.junit.runners.AndroidJUnit4
5
+ import androidx.test.platform.app.InstrumentationRegistry
6
+ import com.facebook.react.bridge.ReadableMap
7
+ import org.junit.After
8
+ import org.junit.Assert.assertEquals
9
+ import org.junit.Assert.assertNull
10
+ import org.junit.Assert.assertTrue
11
+ import org.junit.Before
12
+ import org.junit.Test
13
+ import org.junit.runner.RunWith
14
+ import java.io.File
15
+
16
+ @RunWith(AndroidJUnit4::class)
17
+ class LibyuvResizerModuleExifTest {
18
+
19
+ private lateinit var module: LibyuvResizerModule
20
+ private lateinit var reactContext: FakeReactContext
21
+ private val createdFiles = mutableListOf<String>()
22
+
23
+ @Before
24
+ fun setUp() {
25
+ val ctx = InstrumentationRegistry.getInstrumentation().targetContext
26
+ reactContext = FakeReactContext(ctx)
27
+ module = LibyuvResizerModule(reactContext)
28
+ }
29
+
30
+ @After
31
+ fun tearDown() {
32
+ createdFiles.forEach { TestFixtures.deleteIfExists(it) }
33
+ createdFiles.clear()
34
+ }
35
+
36
+ private fun createJpegWithGps(name: String): String {
37
+ val path = TestFixtures.createJpeg(reactContext, 200, 150, name)
38
+ createdFiles += path
39
+ ExifInterface(path).apply {
40
+ setAttribute(ExifInterface.TAG_GPS_LATITUDE, "48/1,51/1,29/1")
41
+ setAttribute(ExifInterface.TAG_GPS_LATITUDE_REF, "N")
42
+ setAttribute(ExifInterface.TAG_GPS_LONGITUDE, "2/1,17/1,40/1")
43
+ setAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF, "E")
44
+ setAttribute(ExifInterface.TAG_MAKE, "TestCamera")
45
+ setAttribute(ExifInterface.TAG_MODEL, "TestModel")
46
+ setAttribute(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_ROTATE_90.toString())
47
+ }.saveAttributes()
48
+ return path
49
+ }
50
+
51
+ private fun resize(
52
+ filePath: String,
53
+ keepMeta: Boolean,
54
+ quality: Double = 80.0,
55
+ format: String = "jpeg"
56
+ ): FakePromise {
57
+ val promise = FakePromise()
58
+ module.resize(filePath, 100.0, 100.0, quality, 0.0, "contain", "", "box", keepMeta, format, promise)
59
+ return promise
60
+ }
61
+
62
+ @Test
63
+ fun `resize with keepMeta true preserves GPS coordinates`() {
64
+ val src = createJpegWithGps("exif_int_gps_src.jpg")
65
+
66
+ val promise = resize(src, keepMeta = true)
67
+
68
+ assertTrue(promise.resolved)
69
+ val outPath = (promise.result as ReadableMap).getString("path")!!
70
+ createdFiles += outPath
71
+
72
+ val exif = ExifInterface(outPath)
73
+ assertEquals("48/1,51/1,29/1", exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE))
74
+ assertEquals("N", exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF))
75
+ assertEquals("2/1,17/1,40/1", exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE))
76
+ assertEquals("E", exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF))
77
+ }
78
+
79
+ @Test
80
+ fun `resize with keepMeta true resets orientation to normal`() {
81
+ val src = createJpegWithGps("exif_int_orient_src.jpg")
82
+
83
+ val promise = resize(src, keepMeta = true)
84
+
85
+ assertTrue(promise.resolved)
86
+ val outPath = (promise.result as ReadableMap).getString("path")!!
87
+ createdFiles += outPath
88
+
89
+ val exif = ExifInterface(outPath)
90
+ assertEquals(
91
+ ExifInterface.ORIENTATION_NORMAL,
92
+ exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED)
93
+ )
94
+ }
95
+
96
+ @Test
97
+ fun `resize with keepMeta false produces no GPS in output`() {
98
+ val src = createJpegWithGps("exif_int_nometa_src.jpg")
99
+
100
+ val promise = resize(src, keepMeta = false)
101
+
102
+ assertTrue(promise.resolved)
103
+ val outPath = (promise.result as ReadableMap).getString("path")!!
104
+ createdFiles += outPath
105
+
106
+ val exif = ExifInterface(outPath)
107
+ assertNull(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE))
108
+ assertNull(exif.getAttribute(ExifInterface.TAG_MAKE))
109
+ }
110
+
111
+ @Test
112
+ fun `resize with keepMeta omitted default produces no EXIF`() {
113
+ val src = createJpegWithGps("exif_int_default_src.jpg")
114
+
115
+ // keepMeta defaults to false in the bridge call
116
+ val promise = FakePromise()
117
+ module.resize(src, 100.0, 100.0, 80.0, 0.0, "contain", "", "box", false, "jpeg", promise)
118
+
119
+ assertTrue(promise.resolved)
120
+ val outPath = (promise.result as ReadableMap).getString("path")!!
121
+ createdFiles += outPath
122
+
123
+ val exif = ExifInterface(outPath)
124
+ assertNull(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE))
125
+ }
126
+
127
+ @Test
128
+ fun `resize PNG format with keepMeta true resolves without error`() {
129
+ val src = createJpegWithGps("exif_int_png_src.jpg")
130
+
131
+ // format=png → PNG output; keepMeta is no-op for PNG
132
+ val promise = resize(src, keepMeta = true, format = "png")
133
+
134
+ assertTrue("expected resolved, got: ${promise.errorCode}: ${promise.errorMessage}", promise.resolved)
135
+ val outPath = (promise.result as ReadableMap).getString("path")!!
136
+ createdFiles += outPath
137
+ assertTrue("output must be .png", outPath.endsWith(".png"))
138
+ assertTrue("output file must exist", File(outPath).exists())
139
+ }
140
+ }
@@ -0,0 +1,85 @@
1
+ package com.libyuvresizer
2
+
3
+ import androidx.test.ext.junit.runners.AndroidJUnit4
4
+ import androidx.test.platform.app.InstrumentationRegistry
5
+ import org.junit.After
6
+ import org.junit.Assert.assertTrue
7
+ import org.junit.Before
8
+ import org.junit.Test
9
+ import org.junit.runner.RunWith
10
+ import com.facebook.react.bridge.ReadableMap
11
+ import java.io.File
12
+
13
+ @RunWith(AndroidJUnit4::class)
14
+ class LibyuvResizerModuleFilterModeTest {
15
+
16
+ private lateinit var module: LibyuvResizerModule
17
+ private lateinit var reactContext: FakeReactContext
18
+ private val createdFiles = mutableListOf<String>()
19
+ private lateinit var srcPath: String
20
+
21
+ @Before
22
+ fun setUp() {
23
+ val ctx = InstrumentationRegistry.getInstrumentation().targetContext
24
+ reactContext = FakeReactContext(ctx)
25
+ module = LibyuvResizerModule(reactContext)
26
+ srcPath = TestFixtures.createJpeg(reactContext, 120, 80, "filter_src.jpg")
27
+ createdFiles += srcPath
28
+ }
29
+
30
+ @After
31
+ fun tearDown() {
32
+ createdFiles.forEach { TestFixtures.deleteIfExists(it) }
33
+ createdFiles.clear()
34
+ }
35
+
36
+ private fun resizeWithFilter(filterMode: String): FakePromise {
37
+ val promise = FakePromise()
38
+ module.resize(srcPath, 60.0, 60.0, 80.0, 0.0, "contain", "", filterMode, false, "jpeg", promise)
39
+ return promise
40
+ }
41
+
42
+ @Test
43
+ fun resize_filterModeBox_producesValidOutput() {
44
+ val promise = resizeWithFilter("box")
45
+ assertTrue(promise.resolved)
46
+ val outPath = (promise.result as ReadableMap).getString("path")!!
47
+ createdFiles += outPath
48
+ assertTrue(File(outPath).exists())
49
+ val (w, h) = TestFixtures.decodeDimensions(outPath)
50
+ assertTrue(w > 0 && h > 0)
51
+ }
52
+
53
+ @Test
54
+ fun resize_filterModeBilinear_producesValidOutput() {
55
+ val promise = resizeWithFilter("bilinear")
56
+ assertTrue(promise.resolved)
57
+ val outPath = (promise.result as ReadableMap).getString("path")!!
58
+ createdFiles += outPath
59
+ assertTrue(File(outPath).exists())
60
+ val (w, h) = TestFixtures.decodeDimensions(outPath)
61
+ assertTrue(w > 0 && h > 0)
62
+ }
63
+
64
+ @Test
65
+ fun resize_filterModeLinear_producesValidOutput() {
66
+ val promise = resizeWithFilter("linear")
67
+ assertTrue(promise.resolved)
68
+ val outPath = (promise.result as ReadableMap).getString("path")!!
69
+ createdFiles += outPath
70
+ assertTrue(File(outPath).exists())
71
+ val (w, h) = TestFixtures.decodeDimensions(outPath)
72
+ assertTrue(w > 0 && h > 0)
73
+ }
74
+
75
+ @Test
76
+ fun resize_filterModeNone_producesValidOutput() {
77
+ val promise = resizeWithFilter("none")
78
+ assertTrue(promise.resolved)
79
+ val outPath = (promise.result as ReadableMap).getString("path")!!
80
+ createdFiles += outPath
81
+ assertTrue(File(outPath).exists())
82
+ val (w, h) = TestFixtures.decodeDimensions(outPath)
83
+ assertTrue(w > 0 && h > 0)
84
+ }
85
+ }
@@ -0,0 +1,146 @@
1
+ package com.libyuvresizer
2
+
3
+ import androidx.test.ext.junit.runners.AndroidJUnit4
4
+ import androidx.test.platform.app.InstrumentationRegistry
5
+ import com.facebook.react.bridge.ReadableMap
6
+ import org.junit.After
7
+ import org.junit.Assert.assertEquals
8
+ import org.junit.Assert.assertFalse
9
+ import org.junit.Assert.assertTrue
10
+ import org.junit.Before
11
+ import org.junit.Test
12
+ import org.junit.runner.RunWith
13
+ import java.io.File
14
+
15
+ @RunWith(AndroidJUnit4::class)
16
+ class LibyuvResizerModuleFormatTest {
17
+
18
+ private lateinit var module: LibyuvResizerModule
19
+ private lateinit var reactContext: FakeReactContext
20
+ private val createdFiles = mutableListOf<String>()
21
+
22
+ @Before
23
+ fun setUp() {
24
+ val ctx = InstrumentationRegistry.getInstrumentation().targetContext
25
+ reactContext = FakeReactContext(ctx)
26
+ module = LibyuvResizerModule(reactContext)
27
+ }
28
+
29
+ @After
30
+ fun tearDown() {
31
+ createdFiles.forEach { TestFixtures.deleteIfExists(it) }
32
+ createdFiles.clear()
33
+ }
34
+
35
+ private fun resize(filePath: String, format: String, quality: Double = 80.0, keepMeta: Boolean = false): FakePromise {
36
+ val promise = FakePromise()
37
+ module.resize(filePath, 100.0, 100.0, quality, 0.0, "contain", "", "box", keepMeta, format, promise)
38
+ return promise
39
+ }
40
+
41
+ @Test
42
+ fun resize_formatWebp_outputPathEndsWithWebp() {
43
+ val src = TestFixtures.createJpeg(reactContext, 200, 150, "webp_test.jpg")
44
+ createdFiles += src
45
+
46
+ val promise = resize(src, format = "webp")
47
+
48
+ assertTrue("expected resolved, got error=${promise.errorCode}: ${promise.errorMessage}", promise.resolved)
49
+ val outPath = (promise.result as ReadableMap).getString("path")!!
50
+ createdFiles += outPath
51
+ assertTrue("output path must end with .webp", outPath.endsWith(".webp"))
52
+ }
53
+
54
+ @Test
55
+ fun resize_formatWebp_outputFileIsDecodable() {
56
+ val src = TestFixtures.createJpeg(reactContext, 200, 150, "webp_decode.jpg")
57
+ createdFiles += src
58
+
59
+ val promise = resize(src, format = "webp")
60
+
61
+ assertTrue(promise.resolved)
62
+ val outPath = (promise.result as ReadableMap).getString("path")!!
63
+ createdFiles += outPath
64
+ assertTrue("output file must exist", File(outPath).exists())
65
+ assertTrue("output file must be non-empty", File(outPath).length() > 0)
66
+ val (w, h) = TestFixtures.decodeDimensions(outPath)
67
+ assertTrue("decoded WebP width must be > 0", w > 0)
68
+ assertTrue("decoded WebP height must be > 0", h > 0)
69
+ }
70
+
71
+ @Test
72
+ fun resize_formatWebp_resultSizeIsPositive() {
73
+ val src = TestFixtures.createJpeg(reactContext, 200, 150, "webp_size.jpg")
74
+ createdFiles += src
75
+
76
+ val promise = resize(src, format = "webp")
77
+
78
+ assertTrue(promise.resolved)
79
+ val map = promise.result as ReadableMap
80
+ assertTrue("result size must be > 0", map.getDouble("size") > 0)
81
+ }
82
+
83
+ @Test
84
+ fun resize_formatWebp_resultNameEndsWithWebp() {
85
+ val src = TestFixtures.createJpeg(reactContext, 200, 150, "webp_name.jpg")
86
+ createdFiles += src
87
+
88
+ val promise = resize(src, format = "webp")
89
+
90
+ assertTrue(promise.resolved)
91
+ val name = (promise.result as ReadableMap).getString("name")!!
92
+ assertTrue("result name must end with .webp", name.endsWith(".webp"))
93
+ }
94
+
95
+ @Test
96
+ fun resize_formatWebp_keepMetaTrue_resolvesWithoutError() {
97
+ val src = TestFixtures.createJpeg(reactContext, 200, 150, "webp_keepmeta.jpg")
98
+ createdFiles += src
99
+
100
+ val promise = resize(src, format = "webp", keepMeta = true)
101
+
102
+ assertTrue("keepMeta+webp must resolve, got: ${promise.errorCode}: ${promise.errorMessage}", promise.resolved)
103
+ assertFalse(promise.rejected)
104
+ val outPath = (promise.result as ReadableMap).getString("path")!!
105
+ createdFiles += outPath
106
+ assertTrue("output must be .webp", outPath.endsWith(".webp"))
107
+ }
108
+
109
+ @Test
110
+ fun resize_formatJpeg_explicit_outputPathEndsWithJpg() {
111
+ val src = TestFixtures.createJpeg(reactContext, 100, 100, "jpeg_explicit.jpg")
112
+ createdFiles += src
113
+
114
+ val promise = resize(src, format = "jpeg")
115
+
116
+ assertTrue(promise.resolved)
117
+ val outPath = (promise.result as ReadableMap).getString("path")!!
118
+ createdFiles += outPath
119
+ assertTrue("output path must end with .jpg", outPath.endsWith(".jpg"))
120
+ }
121
+
122
+ @Test
123
+ fun resize_formatPng_explicit_outputPathEndsWithPng() {
124
+ val src = TestFixtures.createJpeg(reactContext, 100, 100, "png_explicit.jpg")
125
+ createdFiles += src
126
+
127
+ val promise = resize(src, format = "png")
128
+
129
+ assertTrue(promise.resolved)
130
+ val outPath = (promise.result as ReadableMap).getString("path")!!
131
+ createdFiles += outPath
132
+ assertTrue("output path must end with .png", outPath.endsWith(".png"))
133
+ }
134
+
135
+ @Test
136
+ fun resize_invalidFormat_rejectsWithInvalidFormat() {
137
+ val src = TestFixtures.createJpeg(reactContext, 100, 100, "fmt_err.jpg")
138
+ createdFiles += src
139
+
140
+ val promise = resize(src, format = "gif")
141
+
142
+ assertTrue(promise.rejected)
143
+ assertFalse(promise.resolved)
144
+ assertEquals("E_INVALID_FORMAT", promise.errorCode)
145
+ }
146
+ }
@@ -0,0 +1,157 @@
1
+ package com.libyuvresizer
2
+
3
+ import androidx.test.ext.junit.runners.AndroidJUnit4
4
+ import androidx.test.platform.app.InstrumentationRegistry
5
+ import org.junit.After
6
+ import org.junit.Assert.assertEquals
7
+ import org.junit.Assert.assertTrue
8
+ import org.junit.Before
9
+ import org.junit.Test
10
+ import org.junit.runner.RunWith
11
+ import com.facebook.react.bridge.ReadableMap
12
+ import java.io.File
13
+
14
+ @RunWith(AndroidJUnit4::class)
15
+ class LibyuvResizerModuleIntegrationTest {
16
+
17
+ private lateinit var module: LibyuvResizerModule
18
+ private lateinit var reactContext: FakeReactContext
19
+ private val createdFiles = mutableListOf<String>()
20
+
21
+ @Before
22
+ fun setUp() {
23
+ val ctx = InstrumentationRegistry.getInstrumentation().targetContext
24
+ reactContext = FakeReactContext(ctx)
25
+ module = LibyuvResizerModule(reactContext)
26
+ }
27
+
28
+ @After
29
+ fun tearDown() {
30
+ createdFiles.forEach { TestFixtures.deleteIfExists(it) }
31
+ createdFiles.clear()
32
+ }
33
+
34
+ private fun resize(
35
+ filePath: String,
36
+ targetW: Double,
37
+ targetH: Double,
38
+ quality: Double,
39
+ rotation: Double = 0.0,
40
+ mode: String = "contain",
41
+ outputPath: String = "",
42
+ filterMode: String = "box",
43
+ format: String = "jpeg"
44
+ ): FakePromise {
45
+ val promise = FakePromise()
46
+ module.resize(filePath, targetW, targetH, quality, rotation, mode, outputPath, filterMode, false, format, promise)
47
+ return promise
48
+ }
49
+
50
+ @Test
51
+ fun resize_landscape_contain_returnsScaledJpeg() {
52
+ // 200x100 → target 300x300 contain → scale=min(1.5,3.0)=1.5 → floor(300)x floor(150) = 300x150
53
+ val src = TestFixtures.createJpeg(reactContext, 200, 100, "landscape.jpg")
54
+ createdFiles += src
55
+
56
+ val promise = resize(src, 300.0, 300.0, 80.0)
57
+
58
+ assertTrue("expected resolved, got error=${promise.errorCode}: ${promise.errorMessage}", promise.resolved)
59
+ val outPath = (promise.result as ReadableMap).getString("path")!!
60
+ createdFiles += outPath
61
+ assertTrue("output file must exist", File(outPath).exists())
62
+ assertTrue("output must be .jpg", outPath.endsWith(".jpg"))
63
+ val (w, h) = TestFixtures.decodeDimensions(outPath)
64
+ assertEquals(300, w)
65
+ assertEquals(150, h)
66
+ }
67
+
68
+ @Test
69
+ fun resize_portrait_cover_producesLargerThanTarget() {
70
+ // 100x200 → target 150x150 cover → scale=max(1.5,0.75)=1.5 → round(150)x round(300) = 150x300
71
+ val src = TestFixtures.createJpeg(reactContext, 100, 200, "portrait.jpg")
72
+ createdFiles += src
73
+
74
+ val promise = resize(src, 150.0, 150.0, 80.0, mode = "cover")
75
+
76
+ assertTrue(promise.resolved)
77
+ val outPath = (promise.result as ReadableMap).getString("path")!!
78
+ createdFiles += outPath
79
+ val (w, h) = TestFixtures.decodeDimensions(outPath)
80
+ assertEquals(150, w)
81
+ assertEquals(300, h)
82
+ }
83
+
84
+ @Test
85
+ fun resize_stretch_ignoresAspectRatio() {
86
+ // 200x100 → target 300x150 stretch → output exactly 300x150
87
+ val src = TestFixtures.createJpeg(reactContext, 200, 100, "landscape_stretch.jpg")
88
+ createdFiles += src
89
+
90
+ val promise = resize(src, 300.0, 150.0, 80.0, mode = "stretch")
91
+
92
+ assertTrue(promise.resolved)
93
+ val outPath = (promise.result as ReadableMap).getString("path")!!
94
+ createdFiles += outPath
95
+ val (w, h) = TestFixtures.decodeDimensions(outPath)
96
+ assertEquals(300, w)
97
+ assertEquals(150, h)
98
+ }
99
+
100
+ @Test
101
+ fun resize_formatPng_writesPng() {
102
+ val src = TestFixtures.createJpeg(reactContext, 100, 100, "square.jpg")
103
+ createdFiles += src
104
+
105
+ val promise = resize(src, 50.0, 50.0, 80.0, format = "png")
106
+
107
+ assertTrue(promise.resolved)
108
+ val outPath = (promise.result as ReadableMap).getString("path")!!
109
+ createdFiles += outPath
110
+ assertTrue("output must be .png", outPath.endsWith(".png"))
111
+ val decoded = TestFixtures.decodeDimensions(outPath)
112
+ assertTrue("decoded PNG must be readable", decoded.first > 0 && decoded.second > 0)
113
+ }
114
+
115
+ @Test
116
+ fun resize_quality80_writesJpeg() {
117
+ val src = TestFixtures.createJpeg(reactContext, 100, 100, "square_jpeg.jpg")
118
+ createdFiles += src
119
+
120
+ val promise = resize(src, 50.0, 50.0, 80.0)
121
+
122
+ assertTrue(promise.resolved)
123
+ val outPath = (promise.result as ReadableMap).getString("path")!!
124
+ createdFiles += outPath
125
+ assertTrue("output must be .jpg", outPath.endsWith(".jpg"))
126
+ assertTrue("decoded JPEG must be readable", TestFixtures.decodeDimensions(outPath).first > 0)
127
+ }
128
+
129
+ @Test
130
+ fun resize_smallImageToLargeTarget_contain_upscales() {
131
+ // 64x64 → 400x400 contain → scale=min(400/64, 400/64)=6.25 → floor(400)x floor(400) = 400x400
132
+ val src = TestFixtures.createJpeg(reactContext, 64, 64, "tiny.jpg")
133
+ createdFiles += src
134
+
135
+ val promise = resize(src, 400.0, 400.0, 80.0)
136
+
137
+ assertTrue(promise.resolved)
138
+ val outPath = (promise.result as ReadableMap).getString("path")!!
139
+ createdFiles += outPath
140
+ val (w, h) = TestFixtures.decodeDimensions(outPath)
141
+ assertEquals(400, w)
142
+ assertEquals(400, h)
143
+ }
144
+
145
+ @Test
146
+ fun resize_pngSourceFile_succeeds() {
147
+ val src = TestFixtures.createPng(reactContext, 120, 80, "source.png")
148
+ createdFiles += src
149
+
150
+ val promise = resize(src, 60.0, 60.0, 80.0)
151
+
152
+ assertTrue(promise.resolved)
153
+ val outPath = (promise.result as ReadableMap).getString("path")!!
154
+ createdFiles += outPath
155
+ assertTrue(File(outPath).exists())
156
+ }
157
+ }