mirror of
https://github.com/withastro/astro.git
synced 2024-12-16 21:46:22 -05:00
feat(audits): Handle mutations (#10268)
* feat(audits): Handle mutations * chore: changeset * nit: add comments
This commit is contained in:
parent
0204b7de37
commit
2013e70bce
6 changed files with 224 additions and 3 deletions
5
.changeset/two-ads-bathe.md
Normal file
5
.changeset/two-ads-bathe.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"astro": minor
|
||||
---
|
||||
|
||||
Adds support for page mutations to the audits in the dev toolbar. Astro will now rerun the audits whenever elements are added or deleted from the page.
|
|
@ -44,6 +44,115 @@ test.describe('Dev Toolbar - Audits', () => {
|
|||
await appButton.click();
|
||||
});
|
||||
|
||||
test('can handle mutations', async ({ page, astro }) => {
|
||||
await page.goto(astro.resolveUrl('/audits-mutations'));
|
||||
|
||||
const toolbar = page.locator('astro-dev-toolbar');
|
||||
const appButton = toolbar.locator('button[data-app-id="astro:audit"]');
|
||||
await appButton.click();
|
||||
|
||||
const auditCanvas = toolbar.locator('astro-dev-toolbar-app-canvas[data-app-id="astro:audit"]');
|
||||
const auditHighlights = auditCanvas.locator('astro-dev-toolbar-highlight');
|
||||
await expect(auditHighlights).toHaveCount(1);
|
||||
|
||||
await page.click('body');
|
||||
|
||||
const badButton = page.locator('#bad-button');
|
||||
|
||||
let consolePromise = page.waitForEvent('console');
|
||||
await badButton.click();
|
||||
await consolePromise;
|
||||
|
||||
await appButton.click();
|
||||
await expect(auditHighlights).toHaveCount(2);
|
||||
});
|
||||
|
||||
test('multiple changes only result in one audit update', async ({ page, astro }) => {
|
||||
await page.goto(astro.resolveUrl('/'));
|
||||
|
||||
await page.evaluate(() => {
|
||||
localStorage.setItem(
|
||||
'astro:dev-toolbar:settings',
|
||||
JSON.stringify({
|
||||
verbose: true,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
await page.goto(astro.resolveUrl('/audits-mutations'));
|
||||
|
||||
let logs = [];
|
||||
page.on('console', (msg) => {
|
||||
logs.push(msg.text());
|
||||
});
|
||||
|
||||
const badButton = page.locator('#bad-button');
|
||||
|
||||
let consolePromise = page.waitForEvent('console', (msg) =>
|
||||
msg.text().includes('Rerunning audit lints')
|
||||
);
|
||||
await badButton.click({ clickCount: 5 });
|
||||
await consolePromise;
|
||||
|
||||
await page.click('body');
|
||||
|
||||
expect(
|
||||
logs.filter((log) => log.includes('Rerunning audit lints because the DOM has been updated'))
|
||||
.length === 1
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('handle mutations properly during view transitions', async ({ page, astro }) => {
|
||||
await page.goto(astro.resolveUrl('/'));
|
||||
|
||||
await page.evaluate(() => {
|
||||
localStorage.setItem(
|
||||
'astro:dev-toolbar:settings',
|
||||
JSON.stringify({
|
||||
verbose: true,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
await page.goto(astro.resolveUrl('/audits-mutations'));
|
||||
|
||||
let logs = [];
|
||||
page.on('console', (msg) => {
|
||||
logs.push(msg.text());
|
||||
});
|
||||
|
||||
const linkToOtherPage = page.locator('#link-to-2');
|
||||
let consolePromise = page.waitForEvent('console');
|
||||
await linkToOtherPage.click();
|
||||
await consolePromise;
|
||||
|
||||
const toolbar = page.locator('astro-dev-toolbar');
|
||||
const appButton = toolbar.locator('button[data-app-id="astro:audit"]');
|
||||
|
||||
await appButton.click();
|
||||
|
||||
const auditCanvas = toolbar.locator('astro-dev-toolbar-app-canvas[data-app-id="astro:audit"]');
|
||||
const auditHighlights = auditCanvas.locator('astro-dev-toolbar-highlight');
|
||||
await expect(auditHighlights).toHaveCount(1);
|
||||
|
||||
await page.click('body');
|
||||
|
||||
const badButton = page.locator('#bad-button-2');
|
||||
|
||||
consolePromise = page.waitForEvent('console');
|
||||
await badButton.click();
|
||||
await consolePromise;
|
||||
|
||||
await appButton.click();
|
||||
await expect(auditHighlights).toHaveCount(2);
|
||||
|
||||
// Make sure we only reran audits once
|
||||
expect(
|
||||
logs.filter((log) => log.includes('Rerunning audit lints because the DOM has been updated'))
|
||||
.length === 1
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('does not warn for non-interactive element', async ({ page, astro }) => {
|
||||
await page.goto(astro.resolveUrl('/a11y-exceptions'));
|
||||
|
||||
|
|
|
@ -272,7 +272,6 @@ test.describe('Dev Toolbar', () => {
|
|||
await appButton.click();
|
||||
|
||||
const myAppCanvas = toolbar.locator('astro-dev-toolbar-app-canvas[data-app-id="my-plugin"]');
|
||||
console.log(await myAppCanvas.innerHTML());
|
||||
const myAppWindow = myAppCanvas.locator('astro-dev-toolbar-window');
|
||||
await expect(myAppWindow).toHaveCount(1);
|
||||
await expect(myAppWindow).toBeVisible();
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
import Layout from "../layout/Layout.astro";
|
||||
---
|
||||
|
||||
<Layout>
|
||||
<button id="bad-button-2">Click me to add an image that is missing an alt!</button>
|
||||
<a id="link-to-1" href="/audits-mutations">Go to Mutations 1</a>
|
||||
|
||||
<br /><br /><br />
|
||||
<img src="" width="100" height="100" />
|
||||
|
||||
<script>
|
||||
document.addEventListener('astro:page-load', () => {
|
||||
const badButton = document.getElementById('bad-button-2');
|
||||
if (!badButton) return;
|
||||
|
||||
badButton.addEventListener('click', clickHandler);
|
||||
|
||||
function clickHandler() {
|
||||
const img = document.createElement('img');
|
||||
img.width = 100;
|
||||
img.height = 100;
|
||||
|
||||
document.body.appendChild(img);
|
||||
console.log("Image added to the page")
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</Layout>
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
import Layout from "../layout/Layout.astro";
|
||||
---
|
||||
|
||||
<Layout>
|
||||
<button id="bad-button">Click me to add an image that is missing an alt!</button>
|
||||
<a id="link-to-2" href="/audits-mutations-2">Go to Mutations 2</a>
|
||||
|
||||
<img src="" width="100" height="100" />
|
||||
|
||||
<script>
|
||||
document.addEventListener('astro:page-load', () => {
|
||||
const badButton = document.getElementById('bad-button');
|
||||
if (!badButton) return;
|
||||
|
||||
badButton.addEventListener('click', clickHandler);
|
||||
|
||||
function clickHandler() {
|
||||
const img = document.createElement('img');
|
||||
img.width = 100;
|
||||
img.height = 100;
|
||||
|
||||
document.body.appendChild(img);
|
||||
console.log("Image added to the page")
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</Layout>
|
|
@ -10,6 +10,7 @@ import {
|
|||
import { closeOnOutsideClick, createWindowElement } from '../utils/window.js';
|
||||
import { a11y } from './a11y.js';
|
||||
import { perf } from './perf.js';
|
||||
import { settings } from '../../settings.js';
|
||||
|
||||
const icon =
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 1 20 16"><path fill="#fff" d="M.6 2A1.1 1.1 0 0 1 1.7.9h16.6a1.1 1.1 0 1 1 0 2.2H1.6A1.1 1.1 0 0 1 .8 2Zm1.1 7.1h6a1.1 1.1 0 0 0 0-2.2h-6a1.1 1.1 0 0 0 0 2.2ZM9.3 13H1.8a1.1 1.1 0 1 0 0 2.2h7.5a1.1 1.1 0 1 0 0-2.2Zm11.3 1.9a1.1 1.1 0 0 1-1.5 0l-1.7-1.7a4.1 4.1 0 1 1 1.6-1.6l1.6 1.7a1.1 1.1 0 0 1 0 1.6Zm-5.3-3.4a1.9 1.9 0 1 0 0-3.8 1.9 1.9 0 0 0 0 3.8Z"/></svg>';
|
||||
|
@ -69,8 +70,51 @@ export default {
|
|||
|
||||
await lint();
|
||||
|
||||
document.addEventListener('astro:after-swap', async () => lint());
|
||||
document.addEventListener('astro:page-load', async () => refreshLintPositions);
|
||||
let mutationDebounce: ReturnType<typeof setTimeout>;
|
||||
const observer = new MutationObserver(() => {
|
||||
// We don't want to rerun the audit lints on every single mutation, so we'll debounce it.
|
||||
if (mutationDebounce) {
|
||||
clearTimeout(mutationDebounce);
|
||||
}
|
||||
|
||||
mutationDebounce = setTimeout(() => {
|
||||
settings.logger.verboseLog('Rerunning audit lints because the DOM has been updated.');
|
||||
|
||||
// Even though we're ready to run the lints, we'll wait for the next idle period to do so, as it is less likely
|
||||
// to interfere with any other work the browser is doing post-mutation. For instance, the page or the user might
|
||||
// be interacting with the newly added elements, or the browser might be doing some work (layout, paint, etc.)
|
||||
if ('requestIdleCallback' in window) {
|
||||
window.requestIdleCallback(
|
||||
async () => {
|
||||
lint();
|
||||
},
|
||||
{ timeout: 300 }
|
||||
);
|
||||
} else {
|
||||
// Fallback for old versions of Safari, we'll assume that things are less likely to be busy after 150ms.
|
||||
setTimeout(() => {
|
||||
lint();
|
||||
}, 150);
|
||||
}
|
||||
}, 250);
|
||||
});
|
||||
|
||||
setupObserver();
|
||||
|
||||
document.addEventListener('astro:before-preparation', () => {
|
||||
observer.disconnect();
|
||||
});
|
||||
document.addEventListener('astro:after-swap', async () => {
|
||||
lint();
|
||||
});
|
||||
document.addEventListener('astro:page-load', async () => {
|
||||
refreshLintPositions();
|
||||
|
||||
// HACK: View transitions add a route announcer after this event, so we need to wait for it to be added
|
||||
setTimeout(() => {
|
||||
setupObserver();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
closeOnOutsideClick(eventTarget);
|
||||
|
||||
|
@ -380,5 +424,12 @@ export default {
|
|||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function setupObserver() {
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
} satisfies DevToolbarApp;
|
||||
|
|
Loading…
Reference in a new issue