From 7428bd3a7fff2b95e5885429acc2a50a95e1dfbc Mon Sep 17 00:00:00 2001 From: Pieter Van Poyer Date: Wed, 16 Dec 2020 22:21:35 +0100 Subject: [PATCH] Features/webp support for splashscreen (#1113) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * - linting - platform independent paths in testing - addes some unittest - remove duplication + add comments - delete webp's if png's added, delete png's if webp' added. - Update bin/templates/cordova/lib/prepare.js Co-authored-by: エリス - fix https://github.com/apache/cordova-plugin-splashscreen/issues/257 webp support for android * revert changes * refactor: use source extension for target in getImageResourcePath * fix(prepare): include more extensions in initial splash-screen resource map * tests(prepare): quick-fix for tests * backward slashes must be changed to forward slashes for fast-glob package. Co-authored-by: Raphael von der Grün --- bin/templates/cordova/lib/prepare.js | 44 ++++++--- spec/unit/prepare.spec.js | 137 +++++++++++++++++++++++++++ 2 files changed, 170 insertions(+), 11 deletions(-) diff --git a/bin/templates/cordova/lib/prepare.js b/bin/templates/cordova/lib/prepare.js index 04009943cf..17cbf6f2ed 100644 --- a/bin/templates/cordova/lib/prepare.js +++ b/bin/templates/cordova/lib/prepare.js @@ -302,11 +302,12 @@ function default_versionCode (version) { } function getImageResourcePath (resourcesDir, type, density, name, sourceName) { - if (/\.9\.png$/.test(sourceName)) { - name = name.replace(/\.png$/, '.9.png'); - } - var resourcePath = path.join(resourcesDir, (density ? type + '-' + density : type), name); - return resourcePath; + // Use same extension as source with special case for 9-Patch files + const ext = sourceName.endsWith('.9.png') + ? '.9.png' : path.extname(sourceName).toLowerCase(); + + const subDir = density ? `${type}-${density}` : type; + return path.join(resourcesDir, subDir, name + ext); } function getAdaptiveImageResourcePath (resourcesDir, type, density, name, sourceName) { @@ -317,6 +318,15 @@ function getAdaptiveImageResourcePath (resourcesDir, type, density, name, source return resourcePath; } +function makeSplashCleanupMap (projectRoot, resourcesDir) { + // Build an initial resource map that deletes all existing splash screens + const existingSplashPaths = glob.sync( + `${resourcesDir.replace(/\\/g, '/')}/drawable-*/screen.{png,9.png,webp,jpg,jpeg}`, + { cwd: projectRoot } + ); + return makeCleanResourceMap(existingSplashPaths); +} + function updateSplashes (cordovaProject, platformResourcesDir) { var resources = cordovaProject.projectConfig.getSplashScreens('android'); @@ -326,7 +336,8 @@ function updateSplashes (cordovaProject, platformResourcesDir) { return; } - var resourceMap = mapImageResources(cordovaProject.root, platformResourcesDir, 'drawable', 'screen.png'); + // Build an initial resource map that deletes all existing splash screens + const resourceMap = makeSplashCleanupMap(cordovaProject.root, platformResourcesDir); var hadMdpi = false; resources.forEach(function (resource) { @@ -337,14 +348,14 @@ function updateSplashes (cordovaProject, platformResourcesDir) { hadMdpi = true; } var targetPath = getImageResourcePath( - platformResourcesDir, 'drawable', resource.density, 'screen.png', path.basename(resource.src)); + platformResourcesDir, 'drawable', resource.density, 'screen', path.basename(resource.src)); resourceMap[targetPath] = resource.src; }); // There's no "default" drawable, so assume default == mdpi. if (!hadMdpi && resources.defaultResource) { var targetPath = getImageResourcePath( - platformResourcesDir, 'drawable', 'mdpi', 'screen.png', path.basename(resources.defaultResource.src)); + platformResourcesDir, 'drawable', 'mdpi', 'screen', path.basename(resources.defaultResource.src)); resourceMap[targetPath] = resources.defaultResource.src; } @@ -356,7 +367,8 @@ function updateSplashes (cordovaProject, platformResourcesDir) { function cleanSplashes (projectRoot, projectConfig, platformResourcesDir) { var resources = projectConfig.getSplashScreens('android'); if (resources.length > 0) { - var resourceMap = mapImageResources(projectRoot, platformResourcesDir, 'drawable', 'screen.png'); + const resourceMap = makeSplashCleanupMap(projectRoot, platformResourcesDir); + events.emit('verbose', 'Cleaning splash screens at ' + platformResourcesDir); // No source paths are specified in the map, so updatePaths() will delete the target files. @@ -546,13 +558,13 @@ function updateIconResourceForLegacy (preparedIcons, resourceMap, platformResour // The source paths for icons and splashes are relative to // project's config.xml location, so we use it as base path. for (var density in android_icons) { - var targetPath = getImageResourcePath(platformResourcesDir, 'mipmap', density, 'ic_launcher.png', path.basename(android_icons[density].src)); + var targetPath = getImageResourcePath(platformResourcesDir, 'mipmap', density, 'ic_launcher', path.basename(android_icons[density].src)); resourceMap[targetPath] = android_icons[density].src; } // There's no "default" drawable, so assume default == mdpi. if (default_icon && !android_icons.mdpi) { - var defaultTargetPath = getImageResourcePath(platformResourcesDir, 'mipmap', 'mdpi', 'ic_launcher.png', path.basename(default_icon.src)); + var defaultTargetPath = getImageResourcePath(platformResourcesDir, 'mipmap', 'mdpi', 'ic_launcher', path.basename(default_icon.src)); resourceMap[defaultTargetPath] = default_icon.src; } @@ -673,6 +685,16 @@ function mapImageResources (rootDir, subDir, type, resourceName) { return pathMap; } +/** Returns resource map that deletes all given paths */ +function makeCleanResourceMap (resourcePaths) { + const pathMap = {}; + resourcePaths.map(path.normalize) + .forEach(resourcePath => { + pathMap[resourcePath] = null; + }); + return pathMap; +} + function updateFileResources (cordovaProject, platformDir) { var files = cordovaProject.projectConfig.getFileResources('android'); diff --git a/spec/unit/prepare.spec.js b/spec/unit/prepare.spec.js index a636c2088f..b46be4ce9c 100644 --- a/spec/unit/prepare.spec.js +++ b/spec/unit/prepare.spec.js @@ -81,6 +81,18 @@ function mockGetIconItem (data) { }, data); } +/** + * Create a mock item from the getSplashScreen collection with the supplied updated data. + * + * @param {Object} data Changes to apply to the mock getSplashScreen item + */ +function mockGetSplashScreenItem (data) { + return Object.assign({}, { + src: undefined, + density: undefined + }, data); +} + describe('prepare', () => { describe('updateIcons method', function () { // Rewire @@ -833,4 +845,129 @@ describe('prepare', () => { ).toBeResolved(); }); }); + + describe('updateSplashes method', function () { + // Rewire + let prepare; + + // Spies + let emitSpy; + let updatePathsSpy; + + // Mock Data + let cordovaProject; + let platformResourcesDir; + + beforeEach(function () { + prepare = rewire('../../bin/templates/cordova/lib/prepare'); + + cordovaProject = { + root: '/mock', + projectConfig: { + path: '/mock/config.xml', + cdvNamespacePrefix: 'cdv' + }, + locations: { + plugins: '/mock/plugins', + www: '/mock/www' + } + }; + platformResourcesDir = PATH_RESOURCE; + + emitSpy = jasmine.createSpy('emit'); + prepare.__set__('events', { + emit: emitSpy + }); + + updatePathsSpy = jasmine.createSpy('updatePaths'); + prepare.__set__('FileUpdater', { + updatePaths: updatePathsSpy + }); + + // mocking initial responses for mapImageResources + prepare.__set__('makeSplashCleanupMap', (rootDir, resourcesDir) => ({ + [path.join(resourcesDir, 'drawable-mdpi/screen.png')]: null, + [path.join(resourcesDir, 'drawable-mdpi/screen.webp')]: null + })); + }); + + it('Test#001 : Should detect no defined splash screens.', function () { + const updateSplashes = prepare.__get__('updateSplashes'); + + // mock data. + cordovaProject.projectConfig.getSplashScreens = function (platform) { + return []; + }; + + updateSplashes(cordovaProject, platformResourcesDir); + + // The emit was called + expect(emitSpy).toHaveBeenCalled(); + + // The emit message was. + const actual = emitSpy.calls.argsFor(0)[1]; + const expected = 'This app does not have splash screens defined'; + expect(actual).toEqual(expected); + }); + + it('Test#02 : Should update splash png icon.', function () { + const updateSplashes = prepare.__get__('updateSplashes'); + + // mock data. + cordovaProject.projectConfig.getSplashScreens = function (platform) { + return [mockGetSplashScreenItem({ + density: 'mdpi', + src: 'res/splash/android/mdpi-screen.png' + })]; + }; + + updateSplashes(cordovaProject, platformResourcesDir); + + // The emit was called + expect(emitSpy).toHaveBeenCalled(); + + // The emit message was. + const actual = emitSpy.calls.argsFor(0)[1]; + const expected = 'Updating splash screens at ' + PATH_RESOURCE; + expect(actual).toEqual(expected); + + const actualResourceMap = updatePathsSpy.calls.argsFor(0)[0]; + const expectedResourceMap = {}; + expectedResourceMap[path.join(PATH_RESOURCE, 'drawable-mdpi', 'screen.png')] = 'res/splash/android/mdpi-screen.png'; + expectedResourceMap[path.join(PATH_RESOURCE, 'drawable-mdpi', 'screen.webp')] = null; + + expect(actualResourceMap).toEqual(expectedResourceMap); + }); + + it('Test#03 : Should update splash webp icon.', function () { + const updateSplashes = prepare.__get__('updateSplashes'); + + // mock data. + cordovaProject.projectConfig.getSplashScreens = function (platform) { + return [mockGetSplashScreenItem({ + density: 'mdpi', + src: 'res/splash/android/mdpi-screen.webp' + })]; + }; + + // Creating Spies + updateSplashes(cordovaProject, platformResourcesDir); + + // The emit was called + expect(emitSpy).toHaveBeenCalled(); + + // The emit message was. + const actual = emitSpy.calls.argsFor(0)[1]; + const expected = 'Updating splash screens at ' + PATH_RESOURCE; + expect(actual).toEqual(expected); + + const actualResourceMap = updatePathsSpy.calls.argsFor(0)[0]; + + const expectedResourceMap = {}; + expectedResourceMap[path.join(PATH_RESOURCE, 'drawable-mdpi', 'screen.webp')] = 'res/splash/android/mdpi-screen.webp'; + expectedResourceMap[path.join(PATH_RESOURCE, 'drawable-mdpi', 'screen.png')] = null; + + expect(actualResourceMap).toEqual(expectedResourceMap); + }); + }); });