update tests
This commit is contained in:
parent
bf366ad985
commit
900dff7a1f
30
packages/playwright-core/types/types.d.ts
vendored
30
packages/playwright-core/types/types.d.ts
vendored
|
|
@ -10096,9 +10096,18 @@ export interface Browser {
|
||||||
value: string;
|
value: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* indexedDB to set for context
|
||||||
|
*/
|
||||||
indexedDB?: Array<{
|
indexedDB?: Array<{
|
||||||
|
/**
|
||||||
|
* database name
|
||||||
|
*/
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* database version
|
||||||
|
*/
|
||||||
version: number;
|
version: number;
|
||||||
|
|
||||||
stores: Array<{
|
stores: Array<{
|
||||||
|
|
@ -10123,8 +10132,14 @@ export interface Browser {
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
records: Array<{
|
records: Array<{
|
||||||
|
/**
|
||||||
|
* key, only defined if stores uses out-of-line keys
|
||||||
|
*/
|
||||||
key?: string;
|
key?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* opaque value
|
||||||
|
*/
|
||||||
value: string;
|
value: string;
|
||||||
}>;
|
}>;
|
||||||
}>;
|
}>;
|
||||||
|
|
@ -22297,9 +22312,18 @@ export interface BrowserContextOptions {
|
||||||
value: string;
|
value: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* indexedDB to set for context
|
||||||
|
*/
|
||||||
indexedDB?: Array<{
|
indexedDB?: Array<{
|
||||||
|
/**
|
||||||
|
* database name
|
||||||
|
*/
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* database version
|
||||||
|
*/
|
||||||
version: number;
|
version: number;
|
||||||
|
|
||||||
stores: Array<{
|
stores: Array<{
|
||||||
|
|
@ -22324,8 +22348,14 @@ export interface BrowserContextOptions {
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
records: Array<{
|
records: Array<{
|
||||||
|
/**
|
||||||
|
* key, only defined if stores uses out-of-line keys
|
||||||
|
*/
|
||||||
key?: string;
|
key?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* opaque value
|
||||||
|
*/
|
||||||
value: string;
|
value: string;
|
||||||
}>;
|
}>;
|
||||||
}>;
|
}>;
|
||||||
|
|
|
||||||
1
tests/assets/to-do-notifications/README.md
Normal file
1
tests/assets/to-do-notifications/README.md
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Source: https://github.com/mdn/dom-examples/tree/main/to-do-notifications
|
||||||
108
tests/assets/to-do-notifications/index.html
Normal file
108
tests/assets/to-do-notifications/index.html
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html lang="en-US">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=380">
|
||||||
|
<script src="scripts/todo.js"></script>
|
||||||
|
<title>To-do list with Notifications</title>
|
||||||
|
<link href="style/style.css" type="text/css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h1>To-do list</h1>
|
||||||
|
|
||||||
|
<div class="task-box">
|
||||||
|
|
||||||
|
<ul id="task-list">
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-box">
|
||||||
|
<h2>Add new to-do item.</h2>
|
||||||
|
|
||||||
|
<form id="task-form" action="index.html">
|
||||||
|
<div class="full-width"><label for="title">Task title:</label><input type="text" id="title" required></div>
|
||||||
|
<div class="half-width"><label for="deadline-hours">Hours (hh):</label><input type="number" id="deadline-hours" required></div>
|
||||||
|
<div class="half-width"><label for="deadline-minutes">Mins (mm):</label><input type="number" id="deadline-minutes" required></div>
|
||||||
|
<div class="third-width"><label for="deadline-day">Day:</label>
|
||||||
|
<select id="deadline-day" required>
|
||||||
|
<option value="01">01</option>
|
||||||
|
<option value="02">02</option>
|
||||||
|
<option value="03">03</option>
|
||||||
|
<option value="04">04</option>
|
||||||
|
<option value="05">05</option>
|
||||||
|
<option value="06">06</option>
|
||||||
|
<option value="07">07</option>
|
||||||
|
<option value="08">08</option>
|
||||||
|
<option value="09">09</option>
|
||||||
|
<option value="10">10</option>
|
||||||
|
<option value="11">11</option>
|
||||||
|
<option value="12">12</option>
|
||||||
|
<option value="13">13</option>
|
||||||
|
<option value="14">14</option>
|
||||||
|
<option value="15">15</option>
|
||||||
|
<option value="16">16</option>
|
||||||
|
<option value="17">17</option>
|
||||||
|
<option value="18">18</option>
|
||||||
|
<option value="19">19</option>
|
||||||
|
<option value="20">20</option>
|
||||||
|
<option value="21">21</option>
|
||||||
|
<option value="22">22</option>
|
||||||
|
<option value="23">23</option>
|
||||||
|
<option value="24">24</option>
|
||||||
|
<option value="25">25</option>
|
||||||
|
<option value="26">26</option>
|
||||||
|
<option value="27">27</option>
|
||||||
|
<option value="28">28</option>
|
||||||
|
<option value="29">29</option>
|
||||||
|
<option value="30">30</option>
|
||||||
|
<option value="31">31</option>
|
||||||
|
</select></div>
|
||||||
|
|
||||||
|
<div class="third-width"><label for="deadline-month">Month:</label>
|
||||||
|
<select id="deadline-month" required>
|
||||||
|
<option value="January">January</option>
|
||||||
|
<option value="February">February</option>
|
||||||
|
<option value="March">March</option>
|
||||||
|
<option value="April">April</option>
|
||||||
|
<option value="May">May</option>
|
||||||
|
<option value="June">June</option>
|
||||||
|
<option value="July">July</option>
|
||||||
|
<option value="August">August</option>
|
||||||
|
<option value="September">September</option>
|
||||||
|
<option value="October">October</option>
|
||||||
|
<option value="November">November</option>
|
||||||
|
<option value="December">December</option>
|
||||||
|
</select></div>
|
||||||
|
|
||||||
|
<div class="third-width"><label for="deadline-year">Year:</label>
|
||||||
|
<select id="deadline-year" required>
|
||||||
|
<option value="2025">2025</option>
|
||||||
|
<option value="2024">2024</option>
|
||||||
|
<option value="2023">2023</option>
|
||||||
|
<option value="2022">2022</option>
|
||||||
|
<option value="2021">2021</option>
|
||||||
|
<option value="2020">2020</option>
|
||||||
|
<option value="2019">2019</option>
|
||||||
|
<option value="2018">2018</option>
|
||||||
|
</select></div>
|
||||||
|
|
||||||
|
<div><input type="submit" id="submit" value="Add Task"></div>
|
||||||
|
<div></div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="toolbar">
|
||||||
|
<ul id="notifications">
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<button id="enable">
|
||||||
|
Enable notifications
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
18
tests/assets/to-do-notifications/manifest.webapp
Normal file
18
tests/assets/to-do-notifications/manifest.webapp
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"version": "0.1",
|
||||||
|
"name": "To-do list",
|
||||||
|
"description": "Store to-do items on your device, and be notified when the deadlines are up.",
|
||||||
|
"launch_path": "/to-do-notifications/index.html",
|
||||||
|
"icons": {
|
||||||
|
"128": "/to-do-notifications/img/icon-128.png"
|
||||||
|
},
|
||||||
|
"developer": {
|
||||||
|
"name": "Chris Mills",
|
||||||
|
"url": "http://chrisdavidmills.github.io/to-do-notifications/"
|
||||||
|
},
|
||||||
|
"permissions": {
|
||||||
|
"desktop-notification": {
|
||||||
|
"description": "Needed for creating system notifications."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
354
tests/assets/to-do-notifications/scripts/todo.js
Normal file
354
tests/assets/to-do-notifications/scripts/todo.js
Normal file
|
|
@ -0,0 +1,354 @@
|
||||||
|
window.onload = () => {
|
||||||
|
const MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
|
||||||
|
|
||||||
|
// Hold an instance of a db object for us to store the IndexedDB data in
|
||||||
|
let db;
|
||||||
|
|
||||||
|
// Create a reference to the notifications list in the bottom of the app; we will write database messages into this list by
|
||||||
|
// appending list items as children of this element
|
||||||
|
const note = document.getElementById('notifications');
|
||||||
|
|
||||||
|
// All other UI elements we need for the app
|
||||||
|
const taskList = document.getElementById('task-list');
|
||||||
|
const taskForm = document.getElementById('task-form');
|
||||||
|
const title = document.getElementById('title');
|
||||||
|
const hours = document.getElementById('deadline-hours');
|
||||||
|
const minutes = document.getElementById('deadline-minutes');
|
||||||
|
const day = document.getElementById('deadline-day');
|
||||||
|
const month = document.getElementById('deadline-month');
|
||||||
|
const year = document.getElementById('deadline-year');
|
||||||
|
const notificationBtn = document.getElementById('enable');
|
||||||
|
|
||||||
|
// Do an initial check to see what the notification permission state is
|
||||||
|
if (Notification.permission === 'denied' || Notification.permission === 'default') {
|
||||||
|
notificationBtn.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
notificationBtn.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
note.appendChild(createListItem('App initialised.'));
|
||||||
|
|
||||||
|
// Let us open our database
|
||||||
|
const DBOpenRequest = window.indexedDB.open('toDoList', 4);
|
||||||
|
|
||||||
|
// Register two event handlers to act on the database being opened successfully, or not
|
||||||
|
DBOpenRequest.onerror = (event) => {
|
||||||
|
note.appendChild(createListItem('Error loading database.'));
|
||||||
|
};
|
||||||
|
|
||||||
|
DBOpenRequest.onsuccess = (event) => {
|
||||||
|
note.appendChild(createListItem('Database initialised.'));
|
||||||
|
|
||||||
|
// Store the result of opening the database in the db variable. This is used a lot below
|
||||||
|
db = DBOpenRequest.result;
|
||||||
|
|
||||||
|
// Run the displayData() function to populate the task list with all the to-do list data already in the IndexedDB
|
||||||
|
displayData();
|
||||||
|
};
|
||||||
|
|
||||||
|
// This event handles the event whereby a new version of the database needs to be created
|
||||||
|
// Either one has not been created before, or a new version number has been submitted via the
|
||||||
|
// window.indexedDB.open line above
|
||||||
|
//it is only implemented in recent browsers
|
||||||
|
DBOpenRequest.onupgradeneeded = (event) => {
|
||||||
|
db = event.target.result;
|
||||||
|
|
||||||
|
db.onerror = (event) => {
|
||||||
|
note.appendChild(createListItem('Error loading database.'));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create an objectStore for this database
|
||||||
|
const objectStore = db.createObjectStore('toDoList', { keyPath: 'taskTitle' });
|
||||||
|
|
||||||
|
// Define what data items the objectStore will contain
|
||||||
|
objectStore.createIndex('hours', 'hours', { unique: false });
|
||||||
|
objectStore.createIndex('minutes', 'minutes', { unique: false });
|
||||||
|
objectStore.createIndex('day', 'day', { unique: false });
|
||||||
|
objectStore.createIndex('month', 'month', { unique: false });
|
||||||
|
objectStore.createIndex('year', 'year', { unique: false });
|
||||||
|
|
||||||
|
objectStore.createIndex('notified', 'notified', { unique: false });
|
||||||
|
|
||||||
|
note.appendChild(createListItem('Object store created.'));
|
||||||
|
};
|
||||||
|
|
||||||
|
function displayData() {
|
||||||
|
// First clear the content of the task list so that you don't get a huge long list of duplicate stuff each time
|
||||||
|
// the display is updated.
|
||||||
|
while (taskList.firstChild) {
|
||||||
|
taskList.removeChild(taskList.lastChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open our object store and then get a cursor list of all the different data items in the IDB to iterate through
|
||||||
|
const objectStore = db.transaction('toDoList').objectStore('toDoList');
|
||||||
|
objectStore.openCursor().onsuccess = (event) => {
|
||||||
|
const cursor = event.target.result;
|
||||||
|
// Check if there are no (more) cursor items to iterate through
|
||||||
|
if (!cursor) {
|
||||||
|
// No more items to iterate through, we quit.
|
||||||
|
note.appendChild(createListItem('Entries all displayed.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check which suffix the deadline day of the month needs
|
||||||
|
const { hours, minutes, day, month, year, notified, taskTitle } = cursor.value;
|
||||||
|
const ordDay = ordinal(day);
|
||||||
|
|
||||||
|
// Build the to-do list entry and put it into the list item.
|
||||||
|
const toDoText = `${taskTitle} — ${hours}:${minutes}, ${month} ${ordDay} ${year}.`;
|
||||||
|
const listItem = createListItem(toDoText);
|
||||||
|
|
||||||
|
if (notified === 'yes') {
|
||||||
|
listItem.style.textDecoration = 'line-through';
|
||||||
|
listItem.style.color = 'rgba(255, 0, 0, 0.5)';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put the item item inside the task list
|
||||||
|
taskList.appendChild(listItem);
|
||||||
|
|
||||||
|
// Create a delete button inside each list item,
|
||||||
|
const deleteButton = document.createElement('button');
|
||||||
|
listItem.appendChild(deleteButton);
|
||||||
|
deleteButton.textContent = 'X';
|
||||||
|
|
||||||
|
// Set a data attribute on our delete button to associate the task it relates to.
|
||||||
|
deleteButton.setAttribute('data-task', taskTitle);
|
||||||
|
|
||||||
|
// Associate action (deletion) when clicked
|
||||||
|
deleteButton.onclick = (event) => {
|
||||||
|
deleteItem(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
// continue on to the next item in the cursor
|
||||||
|
cursor.continue();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add listener for clicking the submit button
|
||||||
|
taskForm.addEventListener('submit', addData, false);
|
||||||
|
|
||||||
|
function addData(e) {
|
||||||
|
// Prevent default, as we don't want the form to submit in the conventional way
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Stop the form submitting if any values are left empty.
|
||||||
|
// This should never happen as there is the required attribute
|
||||||
|
if (title.value === '' || hours.value === null || minutes.value === null || day.value === '' || month.value === '' || year.value === null) {
|
||||||
|
note.appendChild(createListItem('Data not submitted — form incomplete.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grab the values entered into the form fields and store them in an object ready for being inserted into the IndexedDB
|
||||||
|
const newItem = [
|
||||||
|
{ taskTitle: title.value, hours: hours.value, minutes: minutes.value, day: day.value, month: month.value, year: year.value, notified: 'no' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Open a read/write DB transaction, ready for adding the data
|
||||||
|
const transaction = db.transaction(['toDoList'], 'readwrite');
|
||||||
|
|
||||||
|
// Report on the success of the transaction completing, when everything is done
|
||||||
|
transaction.oncomplete = () => {
|
||||||
|
note.appendChild(createListItem('Transaction completed: database modification finished.'));
|
||||||
|
|
||||||
|
// Update the display of data to show the newly added item, by running displayData() again.
|
||||||
|
displayData();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handler for any unexpected error
|
||||||
|
transaction.onerror = () => {
|
||||||
|
note.appendChild(createListItem(`Transaction not opened due to error: ${transaction.error}`));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Call an object store that's already been added to the database
|
||||||
|
const objectStore = transaction.objectStore('toDoList');
|
||||||
|
console.log(objectStore.indexNames);
|
||||||
|
console.log(objectStore.keyPath);
|
||||||
|
console.log(objectStore.name);
|
||||||
|
console.log(objectStore.transaction);
|
||||||
|
console.log(objectStore.autoIncrement);
|
||||||
|
|
||||||
|
// Make a request to add our newItem object to the object store
|
||||||
|
const objectStoreRequest = objectStore.add(newItem[0]);
|
||||||
|
objectStoreRequest.onsuccess = (event) => {
|
||||||
|
|
||||||
|
// Report the success of our request
|
||||||
|
// (to detect whether it has been succesfully
|
||||||
|
// added to the database, you'd look at transaction.oncomplete)
|
||||||
|
note.appendChild(createListItem('Request successful.'));
|
||||||
|
|
||||||
|
// Clear the form, ready for adding the next entry
|
||||||
|
title.value = '';
|
||||||
|
hours.value = null;
|
||||||
|
minutes.value = null;
|
||||||
|
day.value = 01;
|
||||||
|
month.value = 'January';
|
||||||
|
year.value = 2020;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
function deleteItem(event) {
|
||||||
|
// Retrieve the name of the task we want to delete
|
||||||
|
const dataTask = event.target.getAttribute('data-task');
|
||||||
|
|
||||||
|
// Open a database transaction and delete the task, finding it by the name we retrieved above
|
||||||
|
const transaction = db.transaction(['toDoList'], 'readwrite');
|
||||||
|
transaction.objectStore('toDoList').delete(dataTask);
|
||||||
|
|
||||||
|
// Report that the data item has been deleted
|
||||||
|
transaction.oncomplete = () => {
|
||||||
|
// Delete the parent of the button, which is the list item, so it is no longer displayed
|
||||||
|
event.target.parentNode.parentNode.removeChild(event.target.parentNode);
|
||||||
|
note.appendChild(createListItem(`Task "${dataTask}" deleted.`));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check whether the deadline for each task is up or not, and responds appropriately
|
||||||
|
function checkDeadlines() {
|
||||||
|
// First of all check whether notifications are enabled or denied
|
||||||
|
if (Notification.permission === 'denied' || Notification.permission === 'default') {
|
||||||
|
notificationBtn.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
notificationBtn.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grab the current time and date
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
// From the now variable, store the current minutes, hours, day of the month, month, year and seconds
|
||||||
|
const minuteCheck = now.getMinutes();
|
||||||
|
const hourCheck = now.getHours();
|
||||||
|
const dayCheck = now.getDate(); // Do not use getDay() that returns the day of the week, 1 to 7
|
||||||
|
const monthCheck = now.getMonth();
|
||||||
|
const yearCheck = now.getFullYear(); // Do not use getYear() that is deprecated.
|
||||||
|
|
||||||
|
// Open a new transaction
|
||||||
|
const objectStore = db.transaction(['toDoList'], 'readwrite').objectStore('toDoList');
|
||||||
|
|
||||||
|
// Open a cursor to iterate through all the data items in the IndexedDB
|
||||||
|
objectStore.openCursor().onsuccess = (event) => {
|
||||||
|
const cursor = event.target.result;
|
||||||
|
if (!cursor) return;
|
||||||
|
const { hours, minutes, day, month, year, notified, taskTitle } = cursor.value;
|
||||||
|
|
||||||
|
// convert the month names we have installed in the IDB into a month number that JavaScript will understand.
|
||||||
|
// The JavaScript date object creates month values as a number between 0 and 11.
|
||||||
|
const monthNumber = MONTHS.indexOf(month);
|
||||||
|
if (monthNumber === -1) throw new Error('Incorrect month entered in database.');
|
||||||
|
|
||||||
|
// Check if the current hours, minutes, day, month and year values match the stored values for each task.
|
||||||
|
// The parseInt() function transforms the value from a string to a number for comparison
|
||||||
|
// (taking care of leading zeros, and removing spaces and underscores from the string).
|
||||||
|
let matched = parseInt(hours) === hourCheck;
|
||||||
|
matched &&= parseInt(minutes) === minuteCheck;
|
||||||
|
matched &&= parseInt(day) === dayCheck;
|
||||||
|
matched &&= parseInt(monthNumber) === monthCheck;
|
||||||
|
matched &&= parseInt(year) === yearCheck;
|
||||||
|
if (matched && notified === 'no') {
|
||||||
|
// If the numbers all do match, run the createNotification() function to create a system notification
|
||||||
|
// but only if the permission is set
|
||||||
|
if (Notification.permission === 'granted') {
|
||||||
|
createNotification(taskTitle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move on to the next cursor item
|
||||||
|
cursor.continue();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ask for permission when the 'Enable notifications' button is clicked
|
||||||
|
function askNotificationPermission() {
|
||||||
|
// Function to actually ask the permissions
|
||||||
|
function handlePermission(permission) {
|
||||||
|
// Whatever the user answers, we make sure Chrome stores the information
|
||||||
|
if (!Reflect.has(Notification, 'permission')) {
|
||||||
|
Notification.permission = permission;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the button to shown or hidden, depending on what the user answers
|
||||||
|
if (Notification.permission === 'denied' || Notification.permission === 'default') {
|
||||||
|
notificationBtn.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
notificationBtn.style.display = 'none';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if the browser supports notifications
|
||||||
|
if (!Reflect.has(window, 'Notification')) {
|
||||||
|
console.log('This browser does not support notifications.');
|
||||||
|
} else {
|
||||||
|
if (checkNotificationPromise()) {
|
||||||
|
Notification.requestPermission().then(handlePermission);
|
||||||
|
} else {
|
||||||
|
Notification.requestPermission(handlePermission);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check whether browser supports the promise version of requestPermission()
|
||||||
|
// Safari only supports the old callback-based version
|
||||||
|
function checkNotificationPromise() {
|
||||||
|
try {
|
||||||
|
Notification.requestPermission().then();
|
||||||
|
} catch(e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Wire up notification permission functionality to 'Enable notifications' button
|
||||||
|
notificationBtn.addEventListener('click', askNotificationPermission);
|
||||||
|
|
||||||
|
function createListItem(contents) {
|
||||||
|
const listItem = document.createElement('li');
|
||||||
|
listItem.textContent = contents;
|
||||||
|
return listItem;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a notification with the given title
|
||||||
|
function createNotification(title) {
|
||||||
|
// Create and show the notification
|
||||||
|
const img = '/to-do-notifications/img/icon-128.png';
|
||||||
|
const text = `HEY! Your task "${title}" is now overdue.`;
|
||||||
|
const notification = new Notification('To do list', { body: text, icon: img });
|
||||||
|
|
||||||
|
// We need to update the value of notified to 'yes' in this particular data object, so the
|
||||||
|
// notification won't be set off on it again
|
||||||
|
|
||||||
|
// First open up a transaction
|
||||||
|
const objectStore = db.transaction(['toDoList'], 'readwrite').objectStore('toDoList');
|
||||||
|
|
||||||
|
// Get the to-do list object that has this title as its title
|
||||||
|
const objectStoreTitleRequest = objectStore.get(title);
|
||||||
|
|
||||||
|
objectStoreTitleRequest.onsuccess = () => {
|
||||||
|
// Grab the data object returned as the result
|
||||||
|
const data = objectStoreTitleRequest.result;
|
||||||
|
|
||||||
|
// Update the notified value in the object to 'yes'
|
||||||
|
data.notified = 'yes';
|
||||||
|
|
||||||
|
// Create another request that inserts the item back into the database
|
||||||
|
const updateTitleRequest = objectStore.put(data);
|
||||||
|
|
||||||
|
// When this new request succeeds, run the displayData() function again to update the display
|
||||||
|
updateTitleRequest.onsuccess = () => {
|
||||||
|
displayData();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Using a setInterval to run the checkDeadlines() function every second
|
||||||
|
setInterval(checkDeadlines, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function returning the day of the month followed by an ordinal (st, nd, or rd)
|
||||||
|
function ordinal(day) {
|
||||||
|
const n = day.toString();
|
||||||
|
const last = n.slice(-1);
|
||||||
|
if (last === '1' && n !== '11') return `${n}st`;
|
||||||
|
if (last === '2' && n !== '12') return `${n}nd`;
|
||||||
|
if (last === '3' && n !== '13') return `${n}rd`;
|
||||||
|
return `${n}th`;
|
||||||
|
};
|
||||||
248
tests/assets/to-do-notifications/style/style.css
Normal file
248
tests/assets/to-do-notifications/style/style.css
Normal file
|
|
@ -0,0 +1,248 @@
|
||||||
|
/* Basic set up + sizing for containers */
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
font-size: 10px;
|
||||||
|
font-family: Georgia, "Times New Roman", Times, serif;
|
||||||
|
background: #111;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
width: 50rem;
|
||||||
|
position: relative;
|
||||||
|
background: #d88;
|
||||||
|
margin: 0 auto;
|
||||||
|
border-left: 2px solid #d33;
|
||||||
|
border-right: 2px solid #d33;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2 {
|
||||||
|
text-align: center;
|
||||||
|
background: #d88;
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 6rem;
|
||||||
|
margin: 0;
|
||||||
|
background: #d66;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 2.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bottom toolbar styling */
|
||||||
|
|
||||||
|
#toolbar {
|
||||||
|
position: relative;
|
||||||
|
height: 6rem;
|
||||||
|
width: 100%;
|
||||||
|
background: #d66;
|
||||||
|
border-top: 2px solid #d33;
|
||||||
|
border-bottom: 2px solid #d33;
|
||||||
|
}
|
||||||
|
|
||||||
|
#enable,
|
||||||
|
input[type="submit"] {
|
||||||
|
line-height: 1.8;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 1px solid black;
|
||||||
|
color: black;
|
||||||
|
text-shadow: 1px 1px 1px black;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
box-shadow:
|
||||||
|
inset 0px 5px 3px rgba(255, 255, 255, 0.2),
|
||||||
|
inset 0px -5px 3px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#enable {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0.3rem;
|
||||||
|
right: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#notifications {
|
||||||
|
margin: 0;
|
||||||
|
position: relative;
|
||||||
|
padding: 0.3rem;
|
||||||
|
background: #ddd;
|
||||||
|
position: absolute;
|
||||||
|
top: 0rem;
|
||||||
|
left: 0rem;
|
||||||
|
height: 5.4rem;
|
||||||
|
width: 50%;
|
||||||
|
overflow: auto;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#notifications li {
|
||||||
|
margin-left: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* New item form styling */
|
||||||
|
|
||||||
|
.form-box {
|
||||||
|
background: #d66;
|
||||||
|
width: 85%;
|
||||||
|
padding: 1rem;
|
||||||
|
margin: 2rem auto;
|
||||||
|
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
form div {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
form .full-width {
|
||||||
|
margin: 1rem auto 2rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
form .half-width {
|
||||||
|
width: 50%;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
form .third-width {
|
||||||
|
width: 33%;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
form div label {
|
||||||
|
width: 10rem;
|
||||||
|
float: left;
|
||||||
|
padding-right: 1rem;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
form .full-width input {
|
||||||
|
width: 30rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
form .half-width input {
|
||||||
|
width: 8.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
form .third-width select {
|
||||||
|
width: 13.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
form div input[type="submit"] {
|
||||||
|
clear: both;
|
||||||
|
width: 20rem;
|
||||||
|
display: block;
|
||||||
|
height: 3rem;
|
||||||
|
margin: 0 auto;
|
||||||
|
position: relative;
|
||||||
|
top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* || tasks box */
|
||||||
|
|
||||||
|
.task-box {
|
||||||
|
width: 85%;
|
||||||
|
padding: 1rem;
|
||||||
|
margin: 2rem auto;
|
||||||
|
font-size: 1.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-box ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-box li {
|
||||||
|
list-style-type: none;
|
||||||
|
padding: 1rem;
|
||||||
|
border-bottom: 2px solid #d33;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-box li:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-box li:last-child {
|
||||||
|
margin-bottom: 0rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-box button {
|
||||||
|
margin-left: 2rem;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: inset 0 -2px 5px rgba(0, 0, 0, 0.5) 1px 1px 1px black;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* setting cursor for interactive controls */
|
||||||
|
|
||||||
|
button,
|
||||||
|
input[type="submit"],
|
||||||
|
select {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* media query for small screens */
|
||||||
|
|
||||||
|
@media (max-width: 32rem) {
|
||||||
|
body {
|
||||||
|
width: 100%;
|
||||||
|
border-left: none;
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
form div {
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
form .full-width {
|
||||||
|
margin: 1rem auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
form .half-width {
|
||||||
|
width: 100%;
|
||||||
|
float: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
form .third-width {
|
||||||
|
width: 100%;
|
||||||
|
float: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
form div label {
|
||||||
|
width: 36%;
|
||||||
|
padding-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
form input,
|
||||||
|
form select,
|
||||||
|
form label {
|
||||||
|
line-height: 2.5rem;
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
form .full-width input {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
form .half-width input {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
form .third-width select {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#enable {
|
||||||
|
right: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -317,8 +317,8 @@ it('should roundtrip local storage in third-party context', async ({ page, conte
|
||||||
await context2.close();
|
await context2.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support IndexedDB', async ({ page, contextFactory }) => {
|
it('should support IndexedDB', async ({ page, server, contextFactory }) => {
|
||||||
await page.goto('https://mdn.github.io/dom-examples/to-do-notifications/');
|
await page.goto(server.PREFIX + '/to-do-notifications/index.html');
|
||||||
await page.getByLabel('Task title').fill('Pet the cat');
|
await page.getByLabel('Task title').fill('Pet the cat');
|
||||||
await page.getByLabel('Hours').fill('1');
|
await page.getByLabel('Hours').fill('1');
|
||||||
await page.getByLabel('Mins').fill('1');
|
await page.getByLabel('Mins').fill('1');
|
||||||
|
|
@ -327,7 +327,7 @@ it('should support IndexedDB', async ({ page, contextFactory }) => {
|
||||||
const storageState = await page.context().storageState();
|
const storageState = await page.context().storageState();
|
||||||
expect(storageState.origins).toEqual([
|
expect(storageState.origins).toEqual([
|
||||||
{
|
{
|
||||||
origin: 'https://mdn.github.io',
|
origin: server.PREFIX,
|
||||||
localStorage: [],
|
localStorage: [],
|
||||||
indexedDB: [
|
indexedDB: [
|
||||||
{
|
{
|
||||||
|
|
@ -403,64 +403,10 @@ it('should support IndexedDB', async ({ page, contextFactory }) => {
|
||||||
expect(await context.storageState()).toEqual(storageState);
|
expect(await context.storageState()).toEqual(storageState);
|
||||||
|
|
||||||
const recreatedPage = await context.newPage();
|
const recreatedPage = await context.newPage();
|
||||||
await recreatedPage.goto('https://mdn.github.io/dom-examples/to-do-notifications/');
|
await recreatedPage.goto(server.PREFIX + '/to-do-notifications/index.html');
|
||||||
await expect(recreatedPage.locator('#task-list')).toMatchAriaSnapshot(`
|
await expect(recreatedPage.locator('#task-list')).toMatchAriaSnapshot(`
|
||||||
- list:
|
- list:
|
||||||
- listitem:
|
- listitem:
|
||||||
- text: /Pet the cat/
|
- text: /Pet the cat/
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('indexedDb firebase acceptance test', async ({ page, contextFactory }) => {
|
|
||||||
await page.goto('https://fir-ui-demo-84a6c.firebaseapp.com/');
|
|
||||||
await page.getByText('Continue as guest').click();
|
|
||||||
await expect(page.getByRole('button', { name: 'Sign Out' })).toBeVisible();
|
|
||||||
await page.close();
|
|
||||||
const storageState = await page.context().storageState();
|
|
||||||
expect(storageState).toEqual({
|
|
||||||
cookies: [],
|
|
||||||
origins: [
|
|
||||||
{
|
|
||||||
indexedDB: [
|
|
||||||
{
|
|
||||||
name: 'firebase-heartbeat-database',
|
|
||||||
stores: [
|
|
||||||
{
|
|
||||||
autoIncrement: false,
|
|
||||||
indexes: [],
|
|
||||||
name: 'firebase-heartbeat-store',
|
|
||||||
records: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
version: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'firebaseLocalStorageDb',
|
|
||||||
stores: [
|
|
||||||
{
|
|
||||||
autoIncrement: false,
|
|
||||||
indexes: [],
|
|
||||||
keyPath: 'fbase_key',
|
|
||||||
name: 'firebaseLocalStorage',
|
|
||||||
records: [
|
|
||||||
{
|
|
||||||
value: expect.any(String),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
version: 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
localStorage: [],
|
|
||||||
origin: 'https://fir-ui-demo-84a6c.firebaseapp.com',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
const recreatedContext = await contextFactory({ storageState });
|
|
||||||
const recreatedPage = await recreatedContext.newPage();
|
|
||||||
await recreatedPage.goto('https://fir-ui-demo-84a6c.firebaseapp.com/');
|
|
||||||
await expect(recreatedPage.getByRole('button', { name: 'Sign Out' })).toBeVisible();
|
|
||||||
expect(await recreatedContext.storageState()).toEqual(storageState);
|
|
||||||
});
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue