UNPKG

booletwa

Version:

Generate TWA projects from a Web Manifest

457 lines (443 loc) 19.6 kB
/* * Copyright 2019 Google Inc. All Rights Reserved. * * Licensed 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. */ import {TwaManifest, TwaManifestJson, asDisplayMode} from '../../lib/TwaManifest'; import {WebManifestJson} from '../../lib/types/WebManifest'; import Color = require('color'); import {ShortcutInfo} from '../../lib/ShortcutInfo'; describe('TwaManifest', () => { describe('#fromWebManifestJson', () => { it('creates a correct TWA manifest', () => { const manifest = { 'name': 'PWA Directory', 'short_name': 'PwaDirectory', 'start_url': '/?utm_source=homescreen', 'display': 'fullscreen', 'orientation': 'landscape', 'icons': [{ 'src': '/favicons/android-chrome-192x192.png', 'sizes': '192x192', 'type': 'image/png', }, { 'src': '/favicons/android-chrome-512x512.png', 'sizes': '512x512', 'type': 'image/png', }], 'theme_color': '#00ff00', 'background_color': '#7cc0ff', 'shortcuts': [{ 'name': 'shortcut name', 'short_name': 'short', 'url': '/launch', 'icons': [{ 'src': '/shortcut_icon.png', 'sizes': '96x96', }], }], }; const manifestUrl = new URL('https://pwa-directory.com/manifest.json'); const twaManifest = TwaManifest.fromWebManifestJson(manifestUrl, manifest as WebManifestJson); expect(twaManifest.packageId).toBe('com.pwa_directory.twa'); expect(twaManifest.name).toBe('PWA Directory'); expect(twaManifest.launcherName).toBe('PwaDirectory'); expect(twaManifest.display).toBe('fullscreen'); expect(twaManifest.orientation).toBe('landscape'); expect(twaManifest.startUrl).toBe('/?utm_source=homescreen'); expect(twaManifest.iconUrl) .toBe('https://pwa-directory.com/favicons/android-chrome-512x512.png'); expect(twaManifest.maskableIconUrl).toBeUndefined(); expect(twaManifest.monochromeIconUrl).toBeUndefined(); expect(twaManifest.themeColor.hex()).toBe('#00FF00'); expect(twaManifest.navigationColor.hex()).toBe('#000000'); expect(twaManifest.navigationColorDark.hex()).toBe('#000000'); expect(twaManifest.navigationDividerColor.hex()).toBe('#000000'); expect(twaManifest.navigationDividerColorDark.hex()).toBe('#000000'); expect(twaManifest.backgroundColor.hex()).toBe('#7CC0FF'); expect(twaManifest.appVersionName).toBe('1'); expect(twaManifest.appVersionCode).toBe(1); expect(twaManifest.signingKey.path).toBe('./android.keystore'); expect(twaManifest.signingKey.alias).toBe('android'); expect(twaManifest.splashScreenFadeOutDuration).toBe(300); expect(twaManifest.enableNotifications).toBeTrue(); expect(twaManifest.webManifestUrl).toEqual(manifestUrl); expect(twaManifest.shortcuts.length).toBe(1); expect(twaManifest.shortcuts[0].name).toBe('shortcut name'); expect(twaManifest.shortcuts[0].shortName).toBe('short'); expect(twaManifest.shortcuts[0].url).toBe('https://pwa-directory.com/launch'); expect(twaManifest.shortcuts[0].chosenIconUrl) .toBe('https://pwa-directory.com/shortcut_icon.png'); expect(twaManifest.generateShortcuts()) .toBe('[[name:\'shortcut name\', short_name:\'short\',' + ' url:\'https://pwa-directory.com/launch\', icon:\'shortcut_0\']]'); expect(twaManifest.fallbackType).toBe('customtabs'); }); it('Sets correct defaults for unavailable fields', () => { const manifest = {}; const manifestUrl = new URL('https://pwa-directory.com/manifest.json'); const twaManifest = TwaManifest.fromWebManifestJson(manifestUrl, manifest); expect(twaManifest.packageId).toBe('com.pwa_directory.twa'); expect(twaManifest.host).toBe('pwa-directory.com'); expect(twaManifest.name).toBe('My TWA'); expect(twaManifest.launcherName).toBe('My TWA'); expect(twaManifest.startUrl).toBe('/'); expect(twaManifest.iconUrl).toBeUndefined(); expect(twaManifest.maskableIconUrl).toBeUndefined(); expect(twaManifest.monochromeIconUrl).toBeUndefined(); expect(twaManifest.display).toBe('standalone'); expect(twaManifest.orientation).toBe('default'); expect(twaManifest.themeColor.hex()).toBe('#FFFFFF'); expect(twaManifest.navigationColor.hex()).toBe('#000000'); expect(twaManifest.navigationColorDark.hex()).toBe('#000000'); expect(twaManifest.navigationDividerColor.hex()).toBe('#000000'); expect(twaManifest.navigationDividerColorDark.hex()).toBe('#000000'); expect(twaManifest.backgroundColor.hex()).toBe('#FFFFFF'); expect(twaManifest.appVersionName).toBe('1'); expect(twaManifest.appVersionCode).toBe(1); expect(twaManifest.signingKey.path).toBe('./android.keystore'); expect(twaManifest.signingKey.alias).toBe('android'); expect(twaManifest.splashScreenFadeOutDuration).toBe(300); expect(twaManifest.enableNotifications).toBeTrue(); expect(twaManifest.webManifestUrl).toEqual(manifestUrl); expect(twaManifest.shortcuts).toEqual([]); expect(twaManifest.generateShortcuts()).toBe('[]'); expect(twaManifest.additionalTrustedOrigins).toEqual([]); expect(twaManifest.retainedBundles).toEqual([]); }); it('Uses "name" when "short_name" is not available', () => { const manifest = { 'name': 'PWA Directory', }; const manifestUrl = new URL('https://pwa-directory.com/manifest.json'); const twaManifest = TwaManifest.fromWebManifestJson(manifestUrl, manifest); expect(twaManifest.name).toBe('PWA Directory'); expect(twaManifest.launcherName).toBe('PWA Director'); }); it('Ignores shortcuts if icon size is empty or too small', () => { const manifest = { 'shortcuts': [{ 'name': 'invalid', 'url': '/invalid', 'icons': [{ 'src': '/no_size.png', }, { 'src': '/small_size.png', 'sizes': '95x95', }], }], }; const manifestUrl = new URL('https://pwa-directory.com/manifest.json'); const twaManifest = TwaManifest.fromWebManifestJson(manifestUrl, manifest); expect(twaManifest.shortcuts).toEqual([]); expect(twaManifest.generateShortcuts()).toBe('[]'); }); it('resolves URLs for maskable and monochrome icons', () => { const manifest = { 'name': 'PWA Directory', 'short_name': 'PwaDirectory', 'start_url': '/?utm_source=homescreen', 'icons': [{ 'src': '/favicons/any.png', 'sizes': '512x512', 'type': 'image/png', 'purpose': 'any', }, { 'src': '/favicons/maskable.png', 'sizes': '512x512', 'type': 'image/png', 'purpose': 'maskable', }, { 'src': '/favicons/monochrome.png', 'sizes': '512x512', 'type': 'image/png', 'purpose': 'monochrome', }], }; const manifestUrl = new URL('https://pwa-directory.com/manifest.json'); const twaManifest = TwaManifest.fromWebManifestJson(manifestUrl, manifest); expect(twaManifest.packageId).toBe('com.pwa_directory.twa'); expect(twaManifest.name).toBe('PWA Directory'); expect(twaManifest.launcherName).toBe('PwaDirectory'); expect(twaManifest.startUrl).toBe('/?utm_source=homescreen'); expect(twaManifest.iconUrl) .toBe('https://pwa-directory.com/favicons/any.png'); expect(twaManifest.maskableIconUrl).toBe('https://pwa-directory.com/favicons/maskable.png'); expect(twaManifest.monochromeIconUrl).toBe('https://pwa-directory.com/favicons/monochrome.png'); }); it('Replaces unsupported display modes with `standalone`', () => { const manifestUrl = new URL('https://pwa-directory.com/manifest.json'); expect(TwaManifest.fromWebManifestJson(manifestUrl, {display: 'minimal-ui'}).display) .toBe('standalone'); expect(TwaManifest.fromWebManifestJson(manifestUrl, {display: 'browser'}).display) .toBe('standalone'); }); }); describe('#constructor', () => { it('Builds a TwaManifest correctly', () => { const twaManifestJson = { packageId: 'com.pwa_directory.twa', host: 'pwa-directory.com', name: 'PWA Directory', launcherName: 'PwaDirectory', startUrl: '/', iconUrl: 'https://pwa-directory.com/favicons/android-chrome-512x512.png', display: 'fullscreen', orientation: 'landscape', themeColor: '#00ff00', navigationColor: '#000000', navigationColorDark: '#ffffff', navigationDividerColor: '#ff0000', navigationDividerColorDark: '#dddddd', backgroundColor: '#0000ff', appVersion: '1.0.0', appVersionCode: 10, signingKey: { path: './my-keystore', alias: 'my-alias', }, splashScreenFadeOutDuration: 300, enableNotifications: true, shortcuts: [{name: 'name', shortName: 'shortName', url: '/', chosenIconUrl: 'icon.png'}], webManifestUrl: 'https://pwa-directory.com/manifest.json', generatorApp: 'test', fallbackType: 'webview', enableSiteSettingsShortcut: false, isChromeOSOnly: false, serviceAccountJsonFile: '/home/service-account.json', additionalTrustedOrigins: ['test.com'], retainedBundles: [3, 4, 5], } as TwaManifestJson; const twaManifest = new TwaManifest(twaManifestJson); expect(twaManifest.packageId).toEqual(twaManifestJson.packageId); expect(twaManifest.host).toEqual(twaManifestJson.host); expect(twaManifest.name).toEqual(twaManifestJson.name); expect(twaManifest.launcherName).toEqual(twaManifest.launcherName); expect(twaManifest.startUrl).toEqual(twaManifest.startUrl); expect(twaManifest.iconUrl).toEqual(twaManifest.iconUrl); expect(twaManifest.display).toEqual('fullscreen'); expect(twaManifest.orientation).toEqual('landscape'); expect(twaManifest.themeColor).toEqual(new Color('#00ff00')); expect(twaManifest.navigationColor).toEqual(new Color('#000000')); expect(twaManifest.navigationColorDark).toEqual(new Color('#ffffff')); expect(twaManifest.navigationDividerColor).toEqual(new Color('#ff0000')); expect(twaManifest.navigationDividerColorDark).toEqual(new Color('#dddddd')); expect(twaManifest.backgroundColor).toEqual(new Color('#0000ff')); expect(twaManifest.appVersionName).toEqual(twaManifestJson.appVersion); expect(twaManifest.appVersionCode).toEqual(twaManifestJson.appVersionCode!); expect(twaManifest.signingKey.path).toEqual(twaManifestJson.signingKey.path); expect(twaManifest.signingKey.alias).toEqual(twaManifestJson.signingKey.alias); expect(twaManifest.splashScreenFadeOutDuration) .toEqual(twaManifestJson.splashScreenFadeOutDuration); expect(twaManifest.enableNotifications).toEqual(twaManifestJson.enableNotifications); expect(twaManifest.shortcuts) .toEqual([new ShortcutInfo('name', 'shortName', '/', 'icon.png')]); expect(twaManifest.webManifestUrl).toEqual(new URL(twaManifestJson.webManifestUrl!)); expect(twaManifest.generatorApp).toEqual(twaManifestJson.generatorApp!); expect(twaManifest.fallbackType).toBe('webview'); expect(twaManifest.enableSiteSettingsShortcut).toEqual(false); expect(twaManifest.isChromeOSOnly).toEqual(false); expect(twaManifest.serviceAccountJsonFile).toEqual(twaManifestJson.serviceAccountJsonFile); expect(twaManifest.additionalTrustedOrigins).toEqual(['test.com']); expect(twaManifest.retainedBundles).toEqual([3, 4, 5]); }); it('Sets correct default values for optional fields', () => { const twaManifestJson = { packageId: 'com.pwa_directory.twa', host: 'pwa-directory.com', name: 'PWA Directory', launcherName: 'PwaDirectory', startUrl: '/', iconUrl: 'https://pwa-directory.com/favicons/android-chrome-512x512.png', themeColor: '#00ff00', navigationColor: '#000000', backgroundColor: '#0000ff', appVersion: '1.0.0', appVersionCode: 10, signingKey: { path: './my-keystore', alias: 'my-alias', }, splashScreenFadeOutDuration: 300, enableNotifications: true, shortcuts: [{name: 'name', url: '/', chosenIconUrl: 'icon.png'}], generatorApp: 'test', } as TwaManifestJson; const twaManifest = new TwaManifest(twaManifestJson); expect(twaManifest.webManifestUrl).toBeUndefined(); expect(twaManifest.fallbackType).toBe('customtabs'); expect(twaManifest.display).toBe('standalone'); expect(twaManifest.enableSiteSettingsShortcut).toEqual(true); expect(twaManifest.navigationColor).toEqual(new Color('#000000')); expect(twaManifest.navigationDividerColor).toEqual(new Color('#00000000')); expect(twaManifest.navigationDividerColorDark).toEqual(new Color('#000000')); }); }); describe('#validate', () => { it('Returns false for an empty TWA Manifest', () => { const twaManifest = new TwaManifest({} as TwaManifestJson); expect(twaManifest.validate()).not.toBeNull(); }); it('Returns true a correct TWA Manifest', () => { const twaManifest = new TwaManifest({ packageId: 'com.pwa_directory.twa', host: 'pwa-directory.com', name: 'PWA Directory', launcherName: 'PwaDirectory', startUrl: '/', iconUrl: 'https://pwa-directory.com/favicons/android-chrome-512x512.png', themeColor: '#00ff00', navigationColor: '#000000', backgroundColor: '#0000ff', appVersion: '1.0.0', signingKey: { path: './android-keystore', alias: 'android', }, splashScreenFadeOutDuration: 300, enableNotifications: true, shortcuts: [{name: 'name', url: '/', chosenIconUrl: 'icon.png'}], } as TwaManifestJson); expect(twaManifest.validate()).toBeNull(); }); }); describe('#asDisplayMode', () => { it('Returns display mode if it is supported', () => { expect(asDisplayMode('standalone')).toBe('standalone'); expect(asDisplayMode('fullscreen')).toBe('fullscreen'); }); it('Returns null for unsupported display modes', () => { expect(asDisplayMode('browser')).toBeNull(); expect(asDisplayMode('minimal-ui')).toBeNull(); expect(asDisplayMode('bogus')).toBeNull(); expect(asDisplayMode('')).toBeNull(); }); }); describe('#merge', () => { it('Validates that the merge is done correctly in case which' + ' there are no fields to ignore', async () => { const webManifest: WebManifestJson = { 'display': 'fullscreen', 'name': 'name', 'short_name': 'different_name', 'start_url': 'https://name.github.io/', 'icons': [{ 'src': 'https://image.png', 'sizes': '512x512', 'purpose': 'any', }, ], }; const twaManifest = new TwaManifest({ 'packageId': 'id', 'host': 'host', 'name': 'name', 'launcherName': 'name', 'display': 'standalone', 'themeColor': '#FFFFFF', 'navigationColor': '#000000', 'navigationColorDark': '#000000', 'navigationDividerColor': '#000000', 'navigationDividerColorDark': '#000000', 'backgroundColor': '#FFFFFF', 'enableNotifications': false, // The start_urls are different, but since they both resolve the same relative // to the host url, nothing changes. 'startUrl': '/', 'iconUrl': 'https://image.png/', 'splashScreenFadeOutDuration': 300, 'signingKey': { 'alias': 'android', 'path': './android.keystore', }, 'appVersionCode': 1, 'shortcuts': [], 'generatorApp': 'bubblewrap-cli', 'webManifestUrl': 'https://name.github.io/', 'fallbackType': 'customtabs', 'features': {}, 'enableSiteSettingsShortcut': true, 'isChromeOSOnly': false, 'appVersion': '1', 'serviceAccountJsonFile': '/home/service-account.json', }); // The versions shouldn't change because the update happens in `cli`. const expectedTwaManifest = new TwaManifest({ ...twaManifest.toJson(), 'launcherName': 'different_name', 'display': 'fullscreen', }); // A URL to insert as the webManifestUrl. const url = new URL('https://name.github.io/'); expect(await TwaManifest.merge([], url, webManifest, twaManifest)) .toEqual(expectedTwaManifest); }); it('Validates that the merge is done correctly in case which' + ' there are fields to ignore', async () => { const webManifest: WebManifestJson = { 'display': 'fullscreen', 'name': 'name', 'short_name': 'different_name', 'start_url': 'https://other_url.github.io/', 'icons': [{ 'src': 'https://image.png', 'sizes': '512x512', 'purpose': 'any', }, ], }; const twaManifest = new TwaManifest({ 'packageId': 'id', 'host': 'host', 'name': 'name', 'launcherName': 'name', 'display': 'standalone', 'themeColor': '#FFFFFF', 'navigationColor': '#000000', 'navigationColorDark': '#000000', 'navigationDividerColor': '#000000', 'navigationDividerColorDark': '#000000', 'backgroundColor': '#FFFFFF', 'enableNotifications': false, // The start_urls are different, but since they both resolve the same relative // to the host url, nothing changes. 'startUrl': '/', 'iconUrl': 'https://image.png/', 'splashScreenFadeOutDuration': 300, 'signingKey': { 'alias': 'android', 'path': './android.keystore', }, 'appVersionCode': 1, 'shortcuts': [], 'generatorApp': 'bubblewrap-cli', 'webManifestUrl': 'https://name.github.io/', 'fallbackType': 'customtabs', 'features': {}, 'enableSiteSettingsShortcut': true, 'isChromeOSOnly': false, 'appVersion': '1', 'serviceAccountJsonFile': '/home/service-account.json', }); // The versions shouldn't change because the update happens in `cli`. const expectedTwaManifest = new TwaManifest({ ...twaManifest.toJson(), 'webManifestUrl': 'https://other_url.github.io/', }); // A URL to insert as the webManifestUrl. const url = new URL('https://name.github.io/'); expect(await TwaManifest.merge(['short_name', 'display'], url, webManifest, twaManifest)) .toEqual(expectedTwaManifest); }); }); });