From a6fecb5d4b273c934069781c65ae5d50a5007a38 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Mon, 28 Feb 2022 16:16:05 -0800 Subject: [PATCH] docs: add mock API project examples (#12244) --- .../mock-battery/demo-battery-api/LICENSE | 22 +++ .../mock-battery/demo-battery-api/README.md | 25 +++ .../mock-battery/demo-battery-api/index.html | 51 ++++++ .../demo-battery-api/src/bolt.png | Bin 0 -> 269 bytes .../demo-battery-api/src/index.js | 71 ++++++++ .../demo-battery-api/src/styles.css | 156 ++++++++++++++++++ examples/mock-battery/package.json | 17 ++ examples/mock-battery/playwright.config.js | 16 ++ .../tests/show-battery-status.spec.js | 26 +++ .../tests/update-battery-status.spec.js | 81 +++++++++ .../mock-battery/tests/verify-calls.spec.js | 39 +++++ examples/mock-filesystem/package.json | 17 ++ examples/mock-filesystem/playwright.config.js | 16 ++ examples/mock-filesystem/src/file-picker.html | 15 ++ examples/mock-filesystem/src/ls-dir.html | 35 ++++ .../tests/directory-reader.spec.js | 64 +++++++ .../mock-filesystem/tests/file-reader.spec.js | 24 +++ 17 files changed, 675 insertions(+) create mode 100644 examples/mock-battery/demo-battery-api/LICENSE create mode 100644 examples/mock-battery/demo-battery-api/README.md create mode 100644 examples/mock-battery/demo-battery-api/index.html create mode 100644 examples/mock-battery/demo-battery-api/src/bolt.png create mode 100644 examples/mock-battery/demo-battery-api/src/index.js create mode 100644 examples/mock-battery/demo-battery-api/src/styles.css create mode 100644 examples/mock-battery/package.json create mode 100644 examples/mock-battery/playwright.config.js create mode 100644 examples/mock-battery/tests/show-battery-status.spec.js create mode 100644 examples/mock-battery/tests/update-battery-status.spec.js create mode 100644 examples/mock-battery/tests/verify-calls.spec.js create mode 100644 examples/mock-filesystem/package.json create mode 100644 examples/mock-filesystem/playwright.config.js create mode 100644 examples/mock-filesystem/src/file-picker.html create mode 100644 examples/mock-filesystem/src/ls-dir.html create mode 100644 examples/mock-filesystem/tests/directory-reader.spec.js create mode 100644 examples/mock-filesystem/tests/file-reader.spec.js diff --git a/examples/mock-battery/demo-battery-api/LICENSE b/examples/mock-battery/demo-battery-api/LICENSE new file mode 100644 index 0000000000..585630c32d --- /dev/null +++ b/examples/mock-battery/demo-battery-api/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2014 Guille Paz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/examples/mock-battery/demo-battery-api/README.md b/examples/mock-battery/demo-battery-api/README.md new file mode 100644 index 0000000000..5a18a73cb4 --- /dev/null +++ b/examples/mock-battery/demo-battery-api/README.md @@ -0,0 +1,25 @@ +# Battery Status API + +> Battery Status API Demo + +## Demo +http://pazguille.github.io/demo-battery-api/ + +## Support +- Chrome 38+ +- Chrome for Android +- Firefox 31+ + +## Specs +http://www.w3.org/TR/battery-status + +## Maintained by +- Guille Paz (Frontender & Web standards lover) +- E-mail: [guille87paz@gmail.com](mailto:guille87paz@gmail.com) +- Twitter: [@pazguille](http://twitter.com/pazguille) +- Web: [http://pazguille.me](http://pazguille.me) + +## License +Licensed under the MIT license. + +Copyright (c) 2014 [@pazguille](http://twitter.com/pazguille). diff --git a/examples/mock-battery/demo-battery-api/index.html b/examples/mock-battery/demo-battery-api/index.html new file mode 100644 index 0000000000..37225351b7 --- /dev/null +++ b/examples/mock-battery/demo-battery-api/index.html @@ -0,0 +1,51 @@ + + + + + Battery Status API - Demo + + + + + + + + +
+

Battery Status API

+ +
+ +
+

Battery Status

+ +
+ + +
+ +
+
Power Source
+
---
+ +
Level percentage
+
---
+ +
Fully charged in
+
---
+ +
Remaining time
+
---
+
+ +
+ + + + + + diff --git a/examples/mock-battery/demo-battery-api/src/bolt.png b/examples/mock-battery/demo-battery-api/src/bolt.png new file mode 100644 index 0000000000000000000000000000000000000000..b9d6504862e612bf40f760cc9e02c4ab263d80ee GIT binary patch literal 269 zcmeAS@N?(olHy`uVBq!ia0vp^!XV7S1|*9D%+3HQ$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWw1G>pfi@Ln>}1B`gs7@#$$d1NT&(EQTO{4bvkG9p{-p)^x=( zANj9o#BtK}|H3T{OV=shS1`HW@S%Ew#L{&Kvjw>At}|!`JXn;i^W@>foerJZ2^Ng= z?bLMGG#NDOWnQfmDn3zRf5nOCfX9IYEEzi%G6!5MWNEw0yojgS?LeK$`dWrZoenmP z85h`-_ReTfwKSP9$K&FmE*s|sY-g9ZDKcGn { + await page.addInitScript(() => { + const mockBattery = { + level: 0.90, + charging: true, + chargingTime: 1800, // seconds + dischargingTime: Infinity, + addEventListener: () => { } + }; + // application tries navigator.battery first + // so we delete this method + delete window.navigator.battery; + // Override the method to always return mock battery info. + window.navigator.getBattery = async () => mockBattery; + }); +}); + +test('show battery status', async ({ page }) => { + await page.goto('/'); + await expect(page.locator('.battery-percentage')).toHaveText('90%'); + await expect(page.locator('.battery-status')).toHaveText('Adapter'); + await expect(page.locator('.battery-fully')).toHaveText('00:30'); +}) diff --git a/examples/mock-battery/tests/update-battery-status.spec.js b/examples/mock-battery/tests/update-battery-status.spec.js new file mode 100644 index 0000000000..73d319a4b4 --- /dev/null +++ b/examples/mock-battery/tests/update-battery-status.spec.js @@ -0,0 +1,81 @@ +// @ts-check +const { test, expect } = require('@playwright/test'); + +let log = []; + +test.beforeEach(async ({page}) => { + log = []; + // Expose function for pushing messages to the Node.js script. + await page.exposeFunction('logCall', msg => log.push(msg)); + + await page.addInitScript(() => { + // for these tests, return the same mock battery status + class BatteryMock { + level = 0.10; + charging = false; + chargingTime = 1800; + dischargingTime = Infinity; + _chargingListeners = []; + _levelListeners = []; + addEventListener(eventName, listener) { + logCall(`addEventListener:${eventName}`); + if (eventName === 'chargingchange') + this._chargingListeners.push(listener); + if (eventName === 'levelchange') + this._levelListeners.push(listener); + } + _setLevel(value) { + this.level = value; + this._levelListeners.forEach(cb => cb()); + } + _setCharging(value) { + this.charging = value; + this._chargingListeners.forEach(cb => cb()); + } + }; + const mockBattery = new BatteryMock(); + // Override the method to always return mock battery info. + window.navigator.getBattery = async () => { + logCall('getBattery'); + return mockBattery; + }; + // Save the mock object on window for easier access. + window.mockBattery = mockBattery; + + // application tries navigator.battery first + // so we delete this method + delete window.navigator.battery; + }); +}); + +test('should update UI when battery status changes', async ({ page }) => { + await page.goto('/'); + await expect(page.locator('.battery-percentage')).toHaveText('10%'); + + // Update level to 27.5% + await page.evaluate(() => window.mockBattery._setLevel(0.275)); + await expect(page.locator('.battery-percentage')).toHaveText('27.5%'); + await expect(page.locator('.battery-status')).toHaveText('Battery'); + + // Emulate connected adapter + await page.evaluate(() => window.mockBattery._setCharging(true)); + await expect(page.locator('.battery-status')).toHaveText('Adapter'); + await expect(page.locator('.battery-fully')).toHaveText('00:30'); +}); + + +test('verify API calls', async ({ page }) => { + await page.goto('/'); + await expect(page.locator('.battery-percentage')).toHaveText('10%'); + + // Ensure expected method calls were made. + expect(log).toEqual([ + 'getBattery', + 'addEventListener:chargingchange', + 'addEventListener:levelchange' + ]); + log = []; // reset the log + + await page.evaluate(() => window.mockBattery._setLevel(0.275)); + expect(log).toEqual([]); // getBattery is not called, cached version is used. +}); diff --git a/examples/mock-battery/tests/verify-calls.spec.js b/examples/mock-battery/tests/verify-calls.spec.js new file mode 100644 index 0000000000..032ff4b37f --- /dev/null +++ b/examples/mock-battery/tests/verify-calls.spec.js @@ -0,0 +1,39 @@ +// @ts-check +const { test, expect } = require('@playwright/test'); + +let log = []; + +test.beforeEach(async ({page}) => { + log = []; + // Expose function for pushing messages to the Node.js script. + await page.exposeFunction('logCall', msg => log.push(msg)); + await page.addInitScript(() => { + const mockBattery = { + level: 0.75, + charging: true, + chargingTime: 1800, // seconds + dischargingTime: Infinity, + addEventListener: (name, cb) => logCall(`addEventListener:${name}`) + }; + // Override the method to always return mock battery info. + window.navigator.getBattery = async () => { + logCall('getBattery'); + return mockBattery; + }; + // application tries navigator.battery first + // so we delete this method + delete window.navigator.battery; + }); +}) + +test('verify battery calls', async ({ page }) => { + await page.goto('/'); + await expect(page.locator('.battery-percentage')).toHaveText('75%'); + + // Ensure expected method calls were made. + expect(log).toEqual([ + 'getBattery', + 'addEventListener:chargingchange', + 'addEventListener:levelchange' + ]); +}); diff --git a/examples/mock-filesystem/package.json b/examples/mock-filesystem/package.json new file mode 100644 index 0000000000..333f434d58 --- /dev/null +++ b/examples/mock-filesystem/package.json @@ -0,0 +1,17 @@ +{ + "name": "mock-filesystem", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "playwright test", + "start": "http-server -c-1 -p 9900 src/" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.19.1", + "http-server": "^14.1.0" + } +} diff --git a/examples/mock-filesystem/playwright.config.js b/examples/mock-filesystem/playwright.config.js new file mode 100644 index 0000000000..9372a46c53 --- /dev/null +++ b/examples/mock-filesystem/playwright.config.js @@ -0,0 +1,16 @@ +// @ts-check +const path = require('path') + +/** + * @see https://playwright.dev/docs/test-configuration + * @type{import('@playwright/test').PlaywrightTestConfig} + */ +const config = { + webServer: { + port: 9900, + command: 'npm run start', + }, + // Test directory + testDir: path.join(__dirname, 'tests'), +}; +module.exports = config; diff --git a/examples/mock-filesystem/src/file-picker.html b/examples/mock-filesystem/src/file-picker.html new file mode 100644 index 0000000000..060ede7b95 --- /dev/null +++ b/examples/mock-filesystem/src/file-picker.html @@ -0,0 +1,15 @@ + + + + + +

Pick a text file and its contents will be shown below

+ + \ No newline at end of file diff --git a/examples/mock-filesystem/src/ls-dir.html b/examples/mock-filesystem/src/ls-dir.html new file mode 100644 index 0000000000..8bb1d7a9fa --- /dev/null +++ b/examples/mock-filesystem/src/ls-dir.html @@ -0,0 +1,35 @@ + + + + + + +

Directory contents:

+
+ \ No newline at end of file diff --git a/examples/mock-filesystem/tests/directory-reader.spec.js b/examples/mock-filesystem/tests/directory-reader.spec.js new file mode 100644 index 0000000000..8933344aab --- /dev/null +++ b/examples/mock-filesystem/tests/directory-reader.spec.js @@ -0,0 +1,64 @@ +// @ts-check +const { test, expect } = require('@playwright/test'); + +test.beforeEach(async ({page}) => { + await page.addInitScript(() => { + class FileSystemHandleMock { + constructor({name, children}) { + this.name = name; + children ??= []; + this.kind = children.length ? 'directory' : 'file'; + this._children = children; + } + + values() { + // Wrap children data in the same mock. + return this._children.map(c => new FileSystemHandleMock(c)); + } + } + // Create mock directory + const mockDir = new FileSystemHandleMock({ + name: 'root', + children: [ + { + name: 'file1', + }, + { + name: 'dir1', + children: [ + { + name: 'file2', + }, + { + name: 'file3', + } + ] + }, + { + name: 'dir2', + children: [ + { + name: 'file4', + }, + { + name: 'file5', + } + ] + } + ] + }); + // Make the picker return mock directory + window.showDirectoryPicker = async () => mockDir; + }); +}); + +test('should display directory tree', async ({ page }) => { + await page.goto('/ls-dir.html'); + await page.locator('button', { hasText: 'Open directory' }).click(); + // Check that the displayed entries match mock directory. + await expect(page.locator('#dir')).toContainText([ + 'file1', + 'dir1', 'file2', 'file3', + 'dir2', 'file4', 'file5' + ]); +}); diff --git a/examples/mock-filesystem/tests/file-reader.spec.js b/examples/mock-filesystem/tests/file-reader.spec.js new file mode 100644 index 0000000000..2a3e0dd770 --- /dev/null +++ b/examples/mock-filesystem/tests/file-reader.spec.js @@ -0,0 +1,24 @@ +// @ts-check +const { test, expect } = require('@playwright/test'); + +test.beforeEach(async ({page}) => { + await page.addInitScript(() => { + class FileSystemFileHandleMock { + constructor(file) { + this._file = file; + } + + async getFile() { + return this._file; + } + } + window.showOpenFilePicker = async () => [new FileSystemFileHandleMock(new File(['Test content.'], "foo.txt"))]; + }); +}); + +test('show file picker with mock class', async ({ page }) => { + await page.goto('/file-picker.html'); + await page.locator('button', { hasText: 'Open File' }).click(); + // Check that the content of the mock file has been loaded. + await expect(page.locator('textarea')).toHaveValue('Test content.'); +});