feat(docs-infra): add shortcuts for the angular.io PWA (#40393)

This commits adds some shortcut definitions for the angular.io PWA. The
user agent can use them to assemble a context menu to be displayed by
the operating system when a user engages with the app's icon. (In
addition, shortcuts provide an easy way for users to add links to
specific pages on their home screen.)

See [here][1] for more details on the `shortcuts` property of the PWA
manifest.

The choice of pages to create shortcuts to was influenced by the
following facts/criteria:
- It seems that only the first 4 shortcuts are displayed by Chrome (at
  least on my Android phone).
- Since the PWA is mostly used on mobile, I omitted pages that are less
  likely to be useful for mobile users (such as pages related to CLI).

[1]: https://developer.mozilla.org/en-US/docs/Web/Manifest/shortcuts

PR Close #40393
This commit is contained in:
George Kalpakas 2021-01-14 16:21:50 +02:00 committed by Andrew Kushnir
parent 4065c98054
commit 96690ed3a4
4 changed files with 131 additions and 2 deletions

View File

@ -22,5 +22,31 @@
"purpose": "maskable" "purpose": "maskable"
} }
], ],
"start_url": "/?utm_source=homescreen" "start_url": "/?utm_source=homescreen",
"shortcuts": [
{
"name": "Go to API Reference",
"short_name": "API",
"description": "Go to the Angular API reference page.",
"url": "/api?utm_source=homescreen"
},
{
"name": "Go to Glossary",
"short_name": "Glossary",
"description": "Go to the glossary page: A list of common Angular terms and their explanation.",
"url": "/guide/glossary?utm_source=homescreen"
},
{
"name": "Go to Resources",
"short_name": "Resources",
"description": "Go to the resources page: A list of Angular resouces, such as development tooling, UI libraries, books, courses, community publications, podcasts, etc.",
"url": "/resources?utm_source=homescreen"
},
{
"name": "Go to Tutorial: Tour of Heroes",
"short_name": "Tutorial",
"description": "Go to the \"Tour of Heroes\" tutorial page: Learn how to create your first Angular application.",
"url": "/tutorial?utm_source=homescreen"
}
]
} }

View File

@ -41,7 +41,7 @@ export class SitePage {
async navigateTo(pageUrl: string) { async navigateTo(pageUrl: string) {
// Navigate to the page, disable animations, and wait for Angular. // Navigate to the page, disable animations, and wait for Angular.
await browser.get(`/${pageUrl}`); await browser.get(`/${pageUrl.replace(/^\//, '')}`);
await browser.executeScript('document.body.classList.add(\'no-animations\')'); await browser.executeScript('document.body.classList.add(\'no-animations\')');
await browser.waitForAngular(); await browser.waitForAngular();
} }

View File

@ -0,0 +1,29 @@
import { PwaManifestPage, PwaShortcutItem } from './pwa-manifest.po';
describe('PWA manifest', () => {
const page = new PwaManifestPage();
describe('shortcuts', () => {
let shortcuts: PwaShortcutItem[];
// Helpers
const pageExists = async (url: string) => {
await page.navigateTo(url);
const content = await page.getDocViewerText();
return !/page not found/i.test(content);
};
beforeEach(async () => {
shortcuts = await page.getPwaShortcuts();
});
it('should exist', async () => {
for (const {short_name, url} of shortcuts) {
expect(await pageExists(url)).toBe(
true,
`Page for shortcut '${short_name}' (from '${page.pwaManifestUrl}') does not exist. (URL: ${url})`);
}
});
});
});

View File

@ -0,0 +1,74 @@
import { get as httpGet } from 'http';
import { get as httpsGet } from 'https';
import { browser } from 'protractor';
import { SitePage } from './app.po';
export type Json = null | boolean | number | string | Json[] | { [key: string]: Json };
/**
* The shape of a PWA manifest.
* For simplicity, we only define types for the properties we care about in tests.
* @see https://developer.mozilla.org/en-US/docs/Web/Manifest
*/
export type PwaManifest = Json & {
shortcuts?: PwaShortcutItem[],
};
/**
* The shape of an item in a PWA manifest's `shortcuts` list.
* @see https://developer.mozilla.org/en-US/docs/Web/Manifest/shortcuts
*/
export type PwaShortcutItem = Json & {
url: string,
name: string,
short_name?: string,
description?: string,
icons?: PwaImageResource[],
};
/**
* The shape of an item in a PWA manifest's icons list (such as the value of the top-level `icons` property or that of
* the `icons` property of a shortcut item).
* @see https://w3c.github.io/manifest/#manifestimageresource-and-its-members
*/
export type PwaImageResource = Json & {
src: string,
sizes?: string,
type?: string,
purpose?: string,
};
export class PwaManifestPage extends SitePage {
/** The base URL with the trailing `/` stripped off (if any). */
baseUrl = browser.baseUrl.replace(/\/$/, '');
/** The URL to the app's PWA manifest. */
pwaManifestUrl = `${this.baseUrl}/pwa-manifest.json`;
private pwaManifestText: string | null = null;
/** Get the app's PWA manifest as an object. */
async getPwaManifest(): Promise<PwaManifest> {
if (this.pwaManifestText === null) {
const get = /^https:/.test(this.pwaManifestUrl) ? httpsGet : httpGet;
this.pwaManifestText = await new Promise<string>((resolve, reject) => {
let responseText = '';
get(this.pwaManifestUrl, res => res
.on('data', chunk => responseText += chunk)
.on('end', () => resolve(responseText))
.on('error', reject));
});
}
return JSON.parse(this.pwaManifestText);
}
/** Get a list of PWA shortcuts as extracted from the app's PWA manifest. */
async getPwaShortcuts(): Promise<PwaShortcutItem[]> {
const {shortcuts = []} = await this.getPwaManifest();
return shortcuts;
}
}