diff --git a/lib/prepare.js b/lib/prepare.js index 722fc6415..b549acdb0 100644 --- a/lib/prepare.js +++ b/lib/prepare.js @@ -363,58 +363,169 @@ function handleBuildSettings (platformConfig, locations, infoPlist) { } function mapIconResources (icons, iconsDir) { - // See https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/MobileHIG/IconMatrix.html - // for launch images sizes reference. + // Ref: https://developer.apple.com/design/human-interface-guidelines/app-icons + // These are ordered according to how Xcode puts them in the Contents.json file const platformIcons = [ - { dest: 'icon-20.png', width: 20, height: 20 }, + // iOS fallback icon sizes { dest: 'icon-20@2x.png', width: 40, height: 40 }, { dest: 'icon-20@3x.png', width: 60, height: 60 }, - { dest: 'icon-40.png', width: 40, height: 40 }, - { dest: 'icon-40@2x.png', width: 80, height: 80 }, - { dest: 'icon-50.png', width: 50, height: 50 }, - { dest: 'icon-50@2x.png', width: 100, height: 100 }, + { dest: 'icon-29@2x.png', width: 58, height: 58 }, + { dest: 'icon-29@3x.png', width: 87, height: 87 }, + { dest: 'icon-38@2x.png', width: 76, height: 76 }, + { dest: 'icon-38@3x.png', width: 114, height: 114 }, + { dest: 'icon-40@2x.png', width: 80, height: 80, target: 'spotlight' }, + { dest: 'icon-40@3x.png', width: 120, height: 120, target: 'spotlight' }, { dest: 'icon-60@2x.png', width: 120, height: 120 }, { dest: 'icon-60@3x.png', width: 180, height: 180 }, - { dest: 'icon-72.png', width: 72, height: 72 }, - { dest: 'icon-72@2x.png', width: 144, height: 144 }, - { dest: 'icon-76.png', width: 76, height: 76 }, + { dest: 'icon-64@2x.png', width: 128, height: 128 }, + { dest: 'icon-64@3x.png', width: 192, height: 192 }, + { dest: 'icon-68@2x.png', width: 136, height: 136 }, { dest: 'icon-76@2x.png', width: 152, height: 152 }, { dest: 'icon-83.5@2x.png', width: 167, height: 167 }, - { dest: 'icon-1024.png', width: 1024, height: 1024 }, - { dest: 'icon-29.png', width: 29, height: 29 }, - { dest: 'icon-29@2x.png', width: 58, height: 58 }, - { dest: 'icon-29@3x.png', width: 87, height: 87 }, - { dest: 'icon.png', width: 57, height: 57 }, - { dest: 'icon@2x.png', width: 114, height: 114 }, - { dest: 'icon-24@2x.png', width: 48, height: 48 }, - { dest: 'icon-27.5@2x.png', width: 55, height: 55 }, - { dest: 'icon-44@2x.png', width: 88, height: 88 }, - { dest: 'icon-86@2x.png', width: 172, height: 172 }, - { dest: 'icon-98@2x.png', width: 196, height: 196 } + + // Default iOS icon + { dest: 'icon.png', width: 1024, height: 1024, useDefault: true }, + + // macOS icon sizes + { dest: 'mac-16.png', width: 16, height: 16, target: 'mac' }, + { dest: 'mac-16@2x.png', width: 32, height: 32, target: 'mac' }, + { dest: 'mac-32.png', width: 32, height: 32, target: 'mac' }, + { dest: 'mac-32@2x.png', width: 64, height: 64, target: 'mac' }, + { dest: 'mac-128.png', width: 128, height: 128, target: 'mac' }, + { dest: 'mac-128@2x.png', width: 256, height: 256, target: 'mac' }, + { dest: 'mac-256.png', width: 256, height: 256, target: 'mac' }, + { dest: 'mac-256@2x.png', width: 512, height: 512, target: 'mac' }, + { dest: 'mac-512.png', width: 512, height: 512, target: 'mac' }, + { dest: 'mac-512@2x.png', width: 1024, height: 1024, target: 'mac' }, + + // WatchOS fallback icon sizes + { dest: 'watchos-22@2x.png', width: 44, height: 44, target: 'watchos' }, + { dest: 'watchos-24@2x.png', width: 48, height: 48, target: 'watchos' }, + { dest: 'watchos-27.5@2x.png', width: 55, height: 55, target: 'watchos' }, + { dest: 'watchos-29@2x.png', width: 58, height: 58, target: 'watchos' }, + { dest: 'watchos-30@2x.png', width: 60, height: 60, target: 'watchos' }, + { dest: 'watchos-32@2x.png', width: 64, height: 64, target: 'watchos' }, + { dest: 'watchos-33@2x.png', width: 66, height: 66, target: 'watchos' }, + { dest: 'watchos-40@2x.png', width: 80, height: 80, target: 'watchos' }, + { dest: 'watchos-43.5@2x.png', width: 87, height: 87, target: 'watchos' }, + { dest: 'watchos-44@2x.png', width: 88, height: 88, target: 'watchos' }, + { dest: 'watchos-46@2x.png', width: 92, height: 92, target: 'watchos' }, + { dest: 'watchos-50@2x.png', width: 100, height: 100, target: 'watchos' }, + { dest: 'watchos-51@2x.png', width: 102, height: 102, target: 'watchos' }, + { dest: 'watchos-54@2x.png', width: 108, height: 108, target: 'watchos' }, + { dest: 'watchos-86@2x.png', width: 172, height: 172, target: 'watchos' }, + { dest: 'watchos-98@2x.png', width: 196, height: 196, target: 'watchos' }, + { dest: 'watchos-108@2x.png', width: 216, height: 216, target: 'watchos' }, + { dest: 'watchos-117@2x.png', width: 234, height: 234, target: 'watchos' }, + { dest: 'watchos-129@2x.png', width: 258, height: 258, target: 'watchos' }, + + // Allow customizing the watchOS icon with target="watchos" + // This falls back to using the iOS app icon by default + { dest: 'watchos.png', width: 1024, height: 1024, target: 'watchos', useDefault: true } ]; const pathMap = {}; + + function getDefaultIconForTarget (target) { + const def = icons.filter(res => !res.width && !res.height && !res.target).pop(); + + if (target) { + return icons + .filter(res => res.target === target) + .filter(res => !res.width && !res.height) + .pop() || def; + } + + return def; + } + + function getIconBySizeAndTarget (width, height, target) { + return icons + .filter(res => res.target === target) + .find(res => + (res.width || res.height) && + (!res.width || (width === res.width)) && + (!res.height || (height === res.height)) + ) || null; + } + platformIcons.forEach(item => { - const icon = icons.getBySize(item.width, item.height) || icons.getDefault(); + const dest = path.join(iconsDir, item.dest); + let icon = getIconBySizeAndTarget(item.width, item.height, item.target); + + if (!icon && item.target === 'spotlight') { + // Fall back to a non-targeted icon + icon = getIconBySizeAndTarget(item.width, item.height); + } + + if (!icon && item.useDefault) { + if (item.target) { + icon = getIconBySizeAndTarget(item.width, item.height); + } + + const defaultIcon = getDefaultIconForTarget(item.target); + if (!icon && defaultIcon) { + icon = defaultIcon; + } + } + if (icon) { - const target = path.join(iconsDir, item.dest); - pathMap[target] = icon.src; + if (icon.src) { + pathMap[dest] = icon.src; + } + + // Only iOS has dark/tinted icon variants + if (!item.target || item.target === 'spotlight') { + if (icon.foreground) { + pathMap[dest.replace('.png', '-dark.png')] = icon.foreground; + } + + if (icon.monochrome) { + pathMap[dest.replace('.png', '-tinted.png')] = icon.monochrome; + } + } } }); + return pathMap; } -function getIconsDir (projectRoot, platformProjDir) { - let iconsDir; - const xcassetsExists = folderExists(path.join(projectRoot, platformProjDir, 'Assets.xcassets')); +function generateAppIconContentsJson (resourceMap) { + const contentsJSON = { + images: [], + info: { + author: 'xcode', + version: 1 + } + }; - if (xcassetsExists) { - iconsDir = path.join(platformProjDir, 'Assets.xcassets', 'AppIcon.appiconset'); - } else { - iconsDir = path.join(platformProjDir, 'Resources', 'icons'); - } + Object.keys(resourceMap).forEach(res => { + const [filename, platform, size, scale, variant] = path.basename(res).match(/([A-Za-z]+)(?:-([0-9.]+)(?:@([0-9.]x))?)?(?:-([a-z]+))?\.png/); + + const entry = { + filename, + idiom: 'universal', + platform: (platform === 'icon') ? 'ios' : platform, + size: `${size ?? 1024}x${size ?? 1024}` + }; - return iconsDir; + if (scale) { + entry.scale = scale; + } + + if (variant) { + entry.appearances = [ + { + appearance: 'luminosity', + value: variant + } + ]; + } + + contentsJSON.images.push(entry); + }); + + return contentsJSON; } function updateIcons (cordovaProject, locations) { @@ -426,18 +537,24 @@ function updateIcons (cordovaProject, locations) { } const platformProjDir = path.relative(cordovaProject.root, locations.xcodeCordovaProj); - const iconsDir = getIconsDir(cordovaProject.root, platformProjDir); + const iconsDir = path.join(platformProjDir, 'Assets.xcassets', 'AppIcon.appiconset'); const resourceMap = mapIconResources(icons, iconsDir); events.emit('verbose', `Updating icons at ${iconsDir}`); FileUpdater.updatePaths( resourceMap, { rootDir: cordovaProject.root }, logFileOp); + + // Now we need to update the AppIcon.appiconset/Contents.json file + const contentsJSON = generateAppIconContentsJson(resourceMap); + + events.emit('verbose', 'Updating App Icon image set contents.json'); + fs.writeFileSync(path.join(cordovaProject.root, iconsDir, 'Contents.json'), JSON.stringify(contentsJSON, null, 2)); } function cleanIcons (projectRoot, projectConfig, locations) { const icons = projectConfig.getIcons('ios'); if (icons.length > 0) { const platformProjDir = path.relative(projectRoot, locations.xcodeCordovaProj); - const iconsDir = getIconsDir(projectRoot, platformProjDir); + const iconsDir = path.join(platformProjDir, 'Assets.xcassets', 'AppIcon.appiconset'); const resourceMap = mapIconResources(icons, iconsDir); Object.keys(resourceMap).forEach(targetIconPath => { resourceMap[targetIconPath] = null; @@ -447,6 +564,16 @@ function cleanIcons (projectRoot, projectConfig, locations) { // Source paths are removed from the map, so updatePaths() will delete the target files. FileUpdater.updatePaths( resourceMap, { rootDir: projectRoot, all: true }, logFileOp); + + const contentsJSON = generateAppIconContentsJson(resourceMap); + + // delete filename from contents.json + contentsJSON.images.forEach(image => { + image.filename = undefined; + }); + + events.emit('verbose', 'Updating App Icon image set contents.json'); + fs.writeFileSync(path.join(projectRoot, iconsDir, 'Contents.json'), JSON.stringify(contentsJSON, null, 2)); } } diff --git a/tests/spec/unit/fixtures/icon-support/configs/multi.xml b/tests/spec/unit/fixtures/icon-support/configs/multi.xml new file mode 100644 index 000000000..504ca2976 --- /dev/null +++ b/tests/spec/unit/fixtures/icon-support/configs/multi.xml @@ -0,0 +1,42 @@ + + + Hello Cordova + + A sample Apache Cordova application that responds to the deviceready event. + + + Apache Cordova Team + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +   + + diff --git a/tests/spec/unit/fixtures/icon-support/configs/none.xml b/tests/spec/unit/fixtures/icon-support/configs/none.xml new file mode 100644 index 000000000..f1b8751cb --- /dev/null +++ b/tests/spec/unit/fixtures/icon-support/configs/none.xml @@ -0,0 +1,24 @@ + + + Hello Cordova + + A sample Apache Cordova application that responds to the deviceready event. + + + Apache Cordova Team + + + + + + + + + + + + + + +   + diff --git a/tests/spec/unit/fixtures/icon-support/configs/single-only.xml b/tests/spec/unit/fixtures/icon-support/configs/single-only.xml new file mode 100644 index 000000000..975364f71 --- /dev/null +++ b/tests/spec/unit/fixtures/icon-support/configs/single-only.xml @@ -0,0 +1,27 @@ + + + Hello Cordova + + A sample Apache Cordova application that responds to the deviceready event. + + + Apache Cordova Team + + + + + + + + + + + + + + + + +   + + diff --git a/tests/spec/unit/fixtures/icon-support/configs/single-variants.xml b/tests/spec/unit/fixtures/icon-support/configs/single-variants.xml new file mode 100644 index 000000000..52cd0d5d6 --- /dev/null +++ b/tests/spec/unit/fixtures/icon-support/configs/single-variants.xml @@ -0,0 +1,27 @@ + + + Hello Cordova + + A sample Apache Cordova application that responds to the deviceready event. + + + Apache Cordova Team + + + + + + + + + + + + + + + + +   + + diff --git a/tests/spec/unit/fixtures/icon-support/contents-json/multi.js b/tests/spec/unit/fixtures/icon-support/contents-json/multi.js new file mode 100644 index 000000000..2b5a317a2 --- /dev/null +++ b/tests/spec/unit/fixtures/icon-support/contents-json/multi.js @@ -0,0 +1,145 @@ +/** + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ + +module.exports = { + "images" : [ + { + "filename": "icon-20@2x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename": "icon-20@3x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename": "icon-29@2x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename": "icon-29@3x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename": "icon-38@2x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "38x38" + }, + { + "filename": "icon-38@3x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "3x", + "size" : "38x38" + }, + { + "filename": "icon-40@2x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename": "icon-40@3x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename": "icon-60@2x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename": "icon-60@3x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename": "icon-64@2x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "64x64" + }, + { + "filename": "icon-64@3x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "3x", + "size" : "64x64" + }, + { + "filename": "icon-68@2x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "68x68" + }, + { + "filename": "icon-76@2x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename": "icon-83.5@2x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "icon.png", + "idiom" : "universal", + "platform": "ios", + "size" : "1024x1024" + }, + { + "filename" : "watchos.png", + "idiom" : "universal", + "platform": "watchos", + "size" : "1024x1024" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +}; + diff --git a/tests/spec/unit/fixtures/icon-support/contents-json/none.js b/tests/spec/unit/fixtures/icon-support/contents-json/none.js new file mode 100644 index 000000000..e08f8af56 --- /dev/null +++ b/tests/spec/unit/fixtures/icon-support/contents-json/none.js @@ -0,0 +1,39 @@ +/** + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ + +module.exports = { + "images" : [ + { + "filename" : "icon.png", + "idiom" : "universal", + "platform": "ios", + "size" : "1024x1024" + }, + { + "filename" : "icon.png", + "idiom" : "universal", + "platform": "watchos", + "size" : "1024x1024" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +}; diff --git a/tests/spec/unit/fixtures/icon-support/contents-json/single-only.js b/tests/spec/unit/fixtures/icon-support/contents-json/single-only.js new file mode 100644 index 000000000..600c3f122 --- /dev/null +++ b/tests/spec/unit/fixtures/icon-support/contents-json/single-only.js @@ -0,0 +1,39 @@ +/** + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ + +module.exports = { + "images" : [ + { + "filename" : "icon.png", + "idiom" : "universal", + "platform": "ios", + "size" : "1024x1024" + }, + { + "filename" : "watchos.png", + "idiom" : "universal", + "platform": "watchos", + "size" : "1024x1024" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +}; diff --git a/tests/spec/unit/fixtures/icon-support/contents-json/single-variants.js b/tests/spec/unit/fixtures/icon-support/contents-json/single-variants.js new file mode 100644 index 000000000..1b3dc3396 --- /dev/null +++ b/tests/spec/unit/fixtures/icon-support/contents-json/single-variants.js @@ -0,0 +1,63 @@ +/** + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ + +module.exports = { + "images" : [ + { + "filename" : "icon.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "icon-dark.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "filename" : "icon-tinted.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "filename" : "watchos.png", + "idiom" : "universal", + "platform" : "watchos", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +}; diff --git a/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-1024x1024@1x.png b/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-1024x1024@1x.png new file mode 100644 index 000000000..8cfcb9741 Binary files /dev/null and b/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-1024x1024@1x.png differ diff --git a/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-20x20@2x.png b/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-20x20@2x.png new file mode 100644 index 000000000..dac813888 Binary files /dev/null and b/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-20x20@2x.png differ diff --git a/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-20x20@3x.png b/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-20x20@3x.png new file mode 100644 index 000000000..79e2e9273 Binary files /dev/null and b/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-20x20@3x.png differ diff --git a/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-29x29@2x.png b/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-29x29@2x.png new file mode 100644 index 000000000..662beedcd Binary files /dev/null and b/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-29x29@2x.png differ diff --git a/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-29x29@3x.png b/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-29x29@3x.png new file mode 100644 index 000000000..9f8bfb093 Binary files /dev/null and b/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-29x29@3x.png differ diff --git a/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-38x38@2x.png b/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-38x38@2x.png new file mode 100644 index 000000000..85a74483f Binary files /dev/null and b/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-38x38@2x.png differ diff --git a/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-38x38@3x.png b/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-38x38@3x.png new file mode 100644 index 000000000..0e0c498d8 Binary files /dev/null and b/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-38x38@3x.png differ diff --git a/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-40x40@2x.png b/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-40x40@2x.png new file mode 100644 index 000000000..601336d4c Binary files /dev/null and b/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-40x40@2x.png differ diff --git a/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-40x40@3x.png b/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-40x40@3x.png new file mode 100644 index 000000000..c2e5c98d7 Binary files /dev/null and b/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-40x40@3x.png differ diff --git a/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-60x60@2x.png b/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-60x60@2x.png new file mode 100644 index 000000000..aef9bf513 Binary files /dev/null and b/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-60x60@2x.png differ diff --git a/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-60x60@3x.png b/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-60x60@3x.png new file mode 100644 index 000000000..d9f5559b5 Binary files /dev/null and b/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-60x60@3x.png differ diff --git a/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-64x64@2x.png b/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-64x64@2x.png new file mode 100644 index 000000000..a436d0351 Binary files /dev/null and b/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-64x64@2x.png differ diff --git a/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-64x64@3x.png b/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-64x64@3x.png new file mode 100644 index 000000000..56482a3be Binary files /dev/null and b/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-64x64@3x.png differ diff --git a/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-68x68@2x.png b/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-68x68@2x.png new file mode 100644 index 000000000..f756ba8c9 Binary files /dev/null and b/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-68x68@2x.png differ diff --git a/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-76x76@2x.png b/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-76x76@2x.png new file mode 100644 index 000000000..303808070 Binary files /dev/null and b/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-76x76@2x.png differ diff --git a/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-83.5x83.5@2x.png b/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-83.5x83.5@2x.png new file mode 100644 index 000000000..efed7886a Binary files /dev/null and b/tests/spec/unit/fixtures/icon-support/res/ios/AppIcon-83.5x83.5@2x.png differ diff --git a/tests/spec/unit/fixtures/icon-support/res/ios/appicon-dark.png b/tests/spec/unit/fixtures/icon-support/res/ios/appicon-dark.png new file mode 100644 index 000000000..884c42316 Binary files /dev/null and b/tests/spec/unit/fixtures/icon-support/res/ios/appicon-dark.png differ diff --git a/tests/spec/unit/fixtures/icon-support/res/ios/appicon-tint.png b/tests/spec/unit/fixtures/icon-support/res/ios/appicon-tint.png new file mode 100644 index 000000000..b37e81158 Binary files /dev/null and b/tests/spec/unit/fixtures/icon-support/res/ios/appicon-tint.png differ diff --git a/tests/spec/unit/fixtures/icon-support/res/ios/appicon.png b/tests/spec/unit/fixtures/icon-support/res/ios/appicon.png new file mode 100644 index 000000000..07afba0e4 Binary files /dev/null and b/tests/spec/unit/fixtures/icon-support/res/ios/appicon.png differ diff --git a/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/Contents.json index d19e65fdb..2c8c083ab 100644 --- a/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,182 +1,20 @@ { "images" : [ { - "idiom" : "iphone", - "size" : "29x29", - "filename" : "icon-small.png", - "scale" : "1x" - }, - { - "idiom" : "iphone", - "size" : "29x29", - "filename" : "icon-small@2x.png", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "29x29", - "filename" : "icon-small@3x.png", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "filename" : "icon-40@2x.png", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "filename" : "icon-60@2x.png", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "57x57", "filename" : "icon.png", - "scale" : "1x" - }, - { - "idiom" : "iphone", - "size" : "57x57", - "filename" : "icon@2x.png", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "filename" : "icon-60@2x.png", - "scale" : "2x" + "idiom" : "universal", + "platform": "ios", + "size" : "1024x1024" }, { - "idiom" : "iphone", - "size" : "60x60", - "filename" : "icon-60@3x.png", - "scale" : "3x" - }, - { - "idiom" : "ipad", - "size" : "29x29", - "filename" : "icon-small.png", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "29x29", - "filename" : "icon-small@2x.png", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "40x40", - "filename" : "icon-40.png", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "40x40", - "filename" : "icon-40@2x.png", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "50x50", - "filename" : "icon-50.png", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "50x50", - "filename" : "icon-50@2x.png", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "72x72", - "filename" : "icon-72.png", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "72x72", - "filename" : "icon-72@2x.png", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "76x76", - "filename" : "icon-76.png", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "76x76", - "filename" : "icon-76@2x.png", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "83.5x83.5", - "filename" : "icon-83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "24x24", - "idiom" : "watch", - "scale" : "2x", - "role" : "notificationCenter", - "subtype" : "38mm" - }, - { - "size" : "27.5x27.5", - "idiom" : "watch", - "scale" : "2x", - "role" : "notificationCenter", - "subtype" : "42mm" - }, - { - "size" : "29x29", - "idiom" : "watch", - "role" : "companionSettings", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "watch", - "role" : "companionSettings", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "watch", - "scale" : "2x", - "role" : "appLauncher", - "subtype" : "38mm" - }, - { - "size" : "44x44", - "idiom" : "watch", - "scale" : "2x", - "role" : "longLook", - "subtype" : "42mm" - }, - { - "size" : "86x86", - "idiom" : "watch", - "scale" : "2x", - "role" : "quickLook", - "subtype" : "38mm" - }, - { - "size" : "98x98", - "idiom" : "watch", - "scale" : "2x", - "role" : "quickLook", - "subtype" : "42mm" + "filename" : "icon.png", + "idiom" : "universal", + "platform": "watchos", + "size" : "1024x1024" } ], "info" : { "version" : 1, "author" : "xcode" } -} \ No newline at end of file +} diff --git a/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-40.png b/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-40.png deleted file mode 100644 index e865adbc5..000000000 Binary files a/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-40.png and /dev/null differ diff --git a/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png b/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png deleted file mode 100644 index 6d07dce53..000000000 Binary files a/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png and /dev/null differ diff --git a/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-50.png b/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-50.png deleted file mode 100644 index 98a9d96d5..000000000 Binary files a/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-50.png and /dev/null differ diff --git a/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-50@2x.png b/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-50@2x.png deleted file mode 100644 index bac693f7f..000000000 Binary files a/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-50@2x.png and /dev/null differ diff --git a/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png b/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png deleted file mode 100644 index 955af362d..000000000 Binary files a/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png and /dev/null differ diff --git a/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png b/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png deleted file mode 100644 index e1268916b..000000000 Binary files a/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png and /dev/null differ diff --git a/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-72.png b/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-72.png deleted file mode 100644 index 8c6e5df34..000000000 Binary files a/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-72.png and /dev/null differ diff --git a/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-72@2x.png b/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-72@2x.png deleted file mode 100644 index dd819da61..000000000 Binary files a/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-72@2x.png and /dev/null differ diff --git a/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-76.png b/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-76.png deleted file mode 100644 index 63afe7f17..000000000 Binary files a/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-76.png and /dev/null differ diff --git a/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png b/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png deleted file mode 100644 index 4cff29a2b..000000000 Binary files a/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png and /dev/null differ diff --git a/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png b/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png deleted file mode 100644 index 3c1a01154..000000000 Binary files a/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png and /dev/null differ diff --git a/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-small.png b/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-small.png deleted file mode 100644 index 0ea1c42f9..000000000 Binary files a/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-small.png and /dev/null differ diff --git a/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-small@2x.png b/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-small@2x.png deleted file mode 100644 index 2c72038ef..000000000 Binary files a/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-small@2x.png and /dev/null differ diff --git a/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-small@3x.png b/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-small@3x.png deleted file mode 100644 index 5c37dfc2a..000000000 Binary files a/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon-small@3x.png and /dev/null differ diff --git a/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon.png b/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon.png index b2571a719..b9250b124 100644 Binary files a/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon.png and b/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon.png differ diff --git a/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon@2x.png b/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon@2x.png deleted file mode 100644 index d75098f5a..000000000 Binary files a/tests/spec/unit/fixtures/ios-config-xml/SampleApp/Assets.xcassets/AppIcon.appiconset/icon@2x.png and /dev/null differ diff --git a/tests/spec/unit/prepare.spec.js b/tests/spec/unit/prepare.spec.js index 769a3afac..836928d8b 100644 --- a/tests/spec/unit/prepare.spec.js +++ b/tests/spec/unit/prepare.spec.js @@ -400,6 +400,326 @@ describe('prepare', () => { }); }); + describe('App Icon handling', () => { + const mapIconResources = prepare.__get__('mapIconResources'); + + describe('#mapIconResources', () => { + it('should handle a default icon', () => { + const icons = [ + { src: 'dummy.png' } + ]; + + const resMap = mapIconResources(icons, ''); + + expect(resMap).toEqual(jasmine.objectContaining({ + 'icon.png': 'dummy.png', + 'watchos.png': 'dummy.png' + })); + }); + + it('should handle a default icon for a watchos target', () => { + const icons = [ + { src: 'dummy.png' }, + { src: 'dummy-watch.png', target: 'watchos' } + ]; + + const resMap = mapIconResources(icons, ''); + + expect(resMap).toEqual(jasmine.objectContaining({ + 'icon.png': 'dummy.png', + 'watchos.png': 'dummy-watch.png' + })); + }); + + it('should handle default icon variants', () => { + const icons = [ + { src: 'dummy.png', monochrome: 'dummy-tint.png', foreground: 'dummy-dark.png' } + ]; + + const resMap = mapIconResources(icons, ''); + + expect(resMap).toEqual(jasmine.objectContaining({ + 'icon.png': 'dummy.png', + 'icon-dark.png': 'dummy-dark.png', + 'icon-tinted.png': 'dummy-tint.png', + 'watchos.png': 'dummy.png' + })); + }); + + it('should handle a single sized icon', () => { + const icons = [ + { src: 'dummy.png', height: 1024, width: 1024 } + ]; + + const resMap = mapIconResources(icons, ''); + + expect(resMap).toEqual(jasmine.objectContaining({ + 'icon.png': 'dummy.png', + 'watchos.png': 'dummy.png' + })); + }); + + it('should handle a single sized icon for watchos target', () => { + const icons = [ + { src: 'dummy.png', height: 1024, width: 1024, target: 'watchos' } + ]; + + const resMap = mapIconResources(icons, ''); + + expect(resMap).toEqual(jasmine.objectContaining({ + 'watchos.png': 'dummy.png' + })); + }); + + it('should handle a sized icon', () => { + const icons = [ + { src: 'dummy.png', height: 120, width: 120 } + ]; + + const resMap = mapIconResources(icons, ''); + + expect(resMap).toEqual(jasmine.objectContaining({ + 'icon-40@3x.png': 'dummy.png', + 'icon-60@2x.png': 'dummy.png' + })); + }); + + it('should handle a sized spotlight icon', () => { + const icons = [ + { src: 'dummy.png', height: 120, width: 120 }, + { src: 'dummy-spot.png', height: 120, width: 120, target: 'spotlight' } + ]; + + const resMap = mapIconResources(icons, ''); + + expect(resMap).toEqual(jasmine.objectContaining({ + 'icon-40@3x.png': 'dummy-spot.png', + 'icon-60@2x.png': 'dummy.png' + })); + }); + + it('should handle sized icon variants', () => { + const icons = [ + { src: 'dummy.png', height: 76, width: 76, monochrome: 'dummy-tint.png', foreground: 'dummy-dark.png' } + ]; + + const resMap = mapIconResources(icons, ''); + + expect(resMap).toEqual(jasmine.objectContaining({ + 'icon-38@2x.png': 'dummy.png', + 'icon-38@2x-dark.png': 'dummy-dark.png', + 'icon-38@2x-tinted.png': 'dummy-tint.png' + })); + }); + + it('should ignore sized watchos icons without a target', () => { + const icons = [ + { src: 'dummy.png', height: 216, width: 216 } + ]; + + const resMap = mapIconResources(icons, ''); + + expect(resMap).toEqual({}); + }); + + it('should handle a sized macOS icon', () => { + const icons = [ + { src: 'dummy.png', height: 256, width: 256, target: 'mac' } + ]; + + const resMap = mapIconResources(icons, ''); + + expect(resMap).toEqual(jasmine.objectContaining({ + 'mac-128@2x.png': 'dummy.png', + 'mac-256.png': 'dummy.png' + })); + }); + + it('should ignore tinted icons for non-iOS targets', () => { + const icons = [ + { monochrome: 'dummy-tint.png', height: 256, width: 256, target: 'mac' }, + { foreground: 'dummy-dark.png', height: 216, width: 216, target: 'watchos' } + ]; + + const resMap = mapIconResources(icons, ''); + + expect(resMap).toEqual({}); + }); + }); + + describe('#updateIcons', () => { + const updateIcons = prepare.__get__('updateIcons'); + const logFileOp = prepare.__get__('logFileOp'); + let iconsDir = ''; + + beforeEach(() => { + const platformProjDir = path.relative(iosProject, p.locations.xcodeCordovaProj); + iconsDir = path.join(platformProjDir, 'Assets.xcassets', 'AppIcon.appiconset'); + }); + + function updateIconsWithConfig (configFile) { + // create a suitable mock project for our method + const project = { + root: iosProject, + locations: p.locations, + projectConfig: new ConfigParser(path.join(FIXTURES, 'icon-support', 'configs', configFile)) + }; + + // copy the icon fixtures to the iOS project + fs.cpSync(path.join(FIXTURES, 'icon-support', 'res'), path.join(iosProject, 'res'), { recursive: true }); + + // copy icons and update Contents.json + updateIcons(project, p.locations); + } + + it('should not update paths if no icons are specified', () => { + const updatePaths = spyOn(FileUpdater, 'updatePaths'); + + updateIconsWithConfig('none.xml'); + + expect(updatePaths).not.toHaveBeenCalled(); + + // verify that that Contents.json is as we expect + const result = JSON.parse(fs.readFileSync(path.join(iosProject, iconsDir, 'Contents.json'))); + expect(result).toEqual(require('./fixtures/icon-support/contents-json/none')); + }); + + it('should update paths if a single icon is specified', () => { + const updatePaths = spyOn(FileUpdater, 'updatePaths'); + + updateIconsWithConfig('single-only.xml'); + + expect(updatePaths).toHaveBeenCalledWith({ + [path.join(iconsDir, 'icon.png')]: 'res/ios/appicon.png', + [path.join(iconsDir, 'watchos.png')]: 'res/ios/appicon.png' + }, { rootDir: iosProject }, logFileOp); + + // verify that that Contents.json is as we expect + const result = JSON.parse(fs.readFileSync(path.join(iosProject, iconsDir, 'Contents.json'))); + expect(result).toEqual(require('./fixtures/icon-support/contents-json/single-only')); + }); + + it('should update paths if a single icon with variants is specified', () => { + const updatePaths = spyOn(FileUpdater, 'updatePaths'); + + updateIconsWithConfig('single-variants.xml'); + + expect(updatePaths).toHaveBeenCalledWith({ + [path.join(iconsDir, 'icon.png')]: 'res/ios/appicon.png', + [path.join(iconsDir, 'icon-dark.png')]: 'res/ios/appicon-dark.png', + [path.join(iconsDir, 'icon-tinted.png')]: 'res/ios/appicon-tint.png', + [path.join(iconsDir, 'watchos.png')]: 'res/ios/appicon.png' + }, { rootDir: iosProject }, logFileOp); + + // verify that that Contents.json is as we expect + const result = JSON.parse(fs.readFileSync(path.join(iosProject, iconsDir, 'Contents.json'))); + expect(result).toEqual(require('./fixtures/icon-support/contents-json/single-variants')); + }); + + it('should update paths if multiple icon sizes are specified', () => { + const updatePaths = spyOn(FileUpdater, 'updatePaths'); + + updateIconsWithConfig('multi.xml'); + + expect(updatePaths).toHaveBeenCalledWith({ + [path.join(iconsDir, 'icon.png')]: 'res/ios/AppIcon-1024x1024@1x.png', + [path.join(iconsDir, 'watchos.png')]: 'res/ios/AppIcon-1024x1024@1x.png', + [path.join(iconsDir, 'icon-20@2x.png')]: 'res/ios/AppIcon-20x20@2x.png', + [path.join(iconsDir, 'icon-20@3x.png')]: 'res/ios/AppIcon-20x20@3x.png', + [path.join(iconsDir, 'icon-29@2x.png')]: 'res/ios/AppIcon-29x29@2x.png', + [path.join(iconsDir, 'icon-29@3x.png')]: 'res/ios/AppIcon-29x29@3x.png', + [path.join(iconsDir, 'icon-38@2x.png')]: 'res/ios/AppIcon-38x38@2x.png', + [path.join(iconsDir, 'icon-38@3x.png')]: 'res/ios/AppIcon-38x38@3x.png', + [path.join(iconsDir, 'icon-40@2x.png')]: 'res/ios/AppIcon-40x40@2x.png', + [path.join(iconsDir, 'icon-40@3x.png')]: 'res/ios/AppIcon-40x40@3x.png', + [path.join(iconsDir, 'icon-60@2x.png')]: 'res/ios/AppIcon-60x60@2x.png', + [path.join(iconsDir, 'icon-60@3x.png')]: 'res/ios/AppIcon-60x60@3x.png', + [path.join(iconsDir, 'icon-64@2x.png')]: 'res/ios/AppIcon-64x64@2x.png', + [path.join(iconsDir, 'icon-64@3x.png')]: 'res/ios/AppIcon-64x64@3x.png', + [path.join(iconsDir, 'icon-68@2x.png')]: 'res/ios/AppIcon-68x68@2x.png', + [path.join(iconsDir, 'icon-76@2x.png')]: 'res/ios/AppIcon-76x76@2x.png', + [path.join(iconsDir, 'icon-83.5@2x.png')]: 'res/ios/AppIcon-83.5x83.5@2x.png' + }, { rootDir: iosProject }, logFileOp); + + // verify that that Contents.json is as we expect + const result = JSON.parse(fs.readFileSync(path.join(iosProject, iconsDir, 'Contents.json'))); + expect(result).toEqual(require('./fixtures/icon-support/contents-json/multi')); + }); + }); + + describe('#cleanIcons', () => { + const updateIcons = prepare.__get__('updateIcons'); + const cleanIcons = prepare.__get__('cleanIcons'); + const logFileOp = prepare.__get__('logFileOp'); + let iconsDir = ''; + + beforeEach(() => { + const platformProjDir = path.relative(iosProject, p.locations.xcodeCordovaProj); + iconsDir = path.join(platformProjDir, 'Assets.xcassets', 'AppIcon.appiconset'); + }); + + it('should remove icon images', () => { + // create a suitable mock project for our method + const project = { + root: iosProject, + locations: p.locations, + projectConfig: new ConfigParser(path.join(FIXTURES, 'icon-support', 'configs', 'multi.xml')) + }; + + // copy the icon fixtures to the iOS project + fs.cpSync(path.join(FIXTURES, 'icon-support', 'res'), path.join(iosProject, 'res'), { recursive: true }); + + // copy icons and update Contents.json + updateIcons(project, p.locations); + + // now, clean the images + const updatePaths = spyOn(FileUpdater, 'updatePaths'); + cleanIcons(iosProject, project.projectConfig, p.locations); + + expect(updatePaths).toHaveBeenCalledWith({ + [path.join(iconsDir, 'icon.png')]: null, + [path.join(iconsDir, 'watchos.png')]: null, + [path.join(iconsDir, 'icon-20@2x.png')]: null, + [path.join(iconsDir, 'icon-20@3x.png')]: null, + [path.join(iconsDir, 'icon-29@2x.png')]: null, + [path.join(iconsDir, 'icon-29@3x.png')]: null, + [path.join(iconsDir, 'icon-38@2x.png')]: null, + [path.join(iconsDir, 'icon-38@3x.png')]: null, + [path.join(iconsDir, 'icon-40@2x.png')]: null, + [path.join(iconsDir, 'icon-40@3x.png')]: null, + [path.join(iconsDir, 'icon-60@2x.png')]: null, + [path.join(iconsDir, 'icon-60@3x.png')]: null, + [path.join(iconsDir, 'icon-64@2x.png')]: null, + [path.join(iconsDir, 'icon-64@3x.png')]: null, + [path.join(iconsDir, 'icon-68@2x.png')]: null, + [path.join(iconsDir, 'icon-76@2x.png')]: null, + [path.join(iconsDir, 'icon-83.5@2x.png')]: null + }, { rootDir: iosProject, all: true }, logFileOp); + }); + + it('should have no effect if no icons are specified', () => { + // create a suitable mock project for our method + const project = { + root: iosProject, + locations: p.locations, + projectConfig: new ConfigParser(path.join(FIXTURES, 'icon-support', 'configs', 'none.xml')) + }; + + // copy the icon fixtures to the iOS project + fs.cpSync(path.join(FIXTURES, 'icon-support', 'res'), path.join(iosProject, 'res'), { recursive: true }); + + // copy icons and update Contents.json + updateIcons(project, p.locations); + + // now, clean the images + const updatePaths = spyOn(FileUpdater, 'updatePaths'); + cleanIcons(iosProject, project.projectConfig, p.locations); + + expect(updatePaths).not.toHaveBeenCalled(); + }); + }); + }); + describe('colorPreferenceToComponents', () => { const colorPreferenceToComponents = prepare.__get__('colorPreferenceToComponents');