feat(KAN-55): support app links, file selection and clipboard
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import process from 'node:process';
|
||||
import { createServer } from 'vite';
|
||||
|
||||
const { chromium } = await import(
|
||||
process.env.PLAYWRIGHT_MODULE ?? 'playwright'
|
||||
);
|
||||
const server = await createServer({
|
||||
configFile: false,
|
||||
optimizeDeps: { noDiscovery: true, include: [] },
|
||||
server: { host: '127.0.0.1', port: 0, hmr: false },
|
||||
appType: 'custom',
|
||||
});
|
||||
server.middlewares.use((request, response, next) => {
|
||||
if (request.url !== '/') {
|
||||
return next();
|
||||
}
|
||||
response.setHeader('Content-Type', 'text/html');
|
||||
response.end(`<!doctype html><html><body>
|
||||
<div role="dialog"><input id="draft" value="unsaved draft"><button id="copy">Copy</button></div>
|
||||
<script type="module">
|
||||
import { copyText } from '/resources/js/lib/clipboard.ts';
|
||||
window.originalClipboard = navigator.clipboard;
|
||||
window.copyText = copyText;
|
||||
document.querySelector('#copy').onclick = async () => {
|
||||
window.result = 'pending';
|
||||
const input = document.querySelector('#draft');
|
||||
input.focus(); input.setSelectionRange(1, 5);
|
||||
try { await copyText('kan55 selected text'); window.result = 'copied'; }
|
||||
catch { window.result = 'failed'; }
|
||||
};
|
||||
</script></body></html>`);
|
||||
});
|
||||
await server.listen();
|
||||
const browser = await chromium.launch({ channel: 'chrome', headless: true });
|
||||
const context = await browser.newContext({
|
||||
permissions: ['clipboard-read', 'clipboard-write'],
|
||||
});
|
||||
const page = await context.newPage();
|
||||
try {
|
||||
await page.goto(`http://127.0.0.1:${server.httpServer.address().port}/`);
|
||||
await page.waitForFunction(() => typeof window.copyText === 'function');
|
||||
for (const mode of ['modern', 'absent', 'denied']) {
|
||||
await page.evaluate(async (mode) => {
|
||||
Object.defineProperty(navigator, 'clipboard', {
|
||||
configurable: true,
|
||||
value:
|
||||
mode === 'modern'
|
||||
? window.originalClipboard
|
||||
: mode === 'absent'
|
||||
? undefined
|
||||
: {
|
||||
writeText: () =>
|
||||
Promise.reject(
|
||||
new DOMException(
|
||||
'Denied',
|
||||
'NotAllowedError',
|
||||
),
|
||||
),
|
||||
},
|
||||
});
|
||||
await window.originalClipboard.writeText('previous');
|
||||
// Simulate the modal's focus boundary used by the two-factor setup dialog.
|
||||
document.onfocusin = (event) => {
|
||||
if (!event.target.closest('[role="dialog"]')) {
|
||||
document.querySelector('#draft').focus();
|
||||
}
|
||||
};
|
||||
}, mode);
|
||||
await page.getByRole('button', { name: 'Copy', exact: true }).click();
|
||||
await page.waitForFunction(() => window.result === 'copied');
|
||||
assert.equal(
|
||||
await page.evaluate(() => window.originalClipboard.readText()),
|
||||
'kan55 selected text',
|
||||
mode,
|
||||
);
|
||||
assert.deepEqual(
|
||||
await page.evaluate(() => {
|
||||
const input = document.querySelector('#draft');
|
||||
return [
|
||||
document.activeElement === input,
|
||||
input.value,
|
||||
input.selectionStart,
|
||||
input.selectionEnd,
|
||||
document.querySelectorAll('textarea').length,
|
||||
];
|
||||
}),
|
||||
[true, 'unsaved draft', 1, 5, 0],
|
||||
mode,
|
||||
);
|
||||
}
|
||||
await page.evaluate(() => {
|
||||
document.execCommand = () => false;
|
||||
});
|
||||
await page.getByRole('button', { name: 'Copy', exact: true }).click();
|
||||
await page.waitForFunction(() => window.result === 'failed');
|
||||
assert.equal(await page.locator('textarea').count(), 0);
|
||||
assert.equal(await page.locator('#draft').inputValue(), 'unsaved draft');
|
||||
console.log(
|
||||
'Clipboard browser checks passed: async API, unavailable/denied API fallback, modal focus, input selection and failure cleanup.',
|
||||
);
|
||||
} finally {
|
||||
await browser.close();
|
||||
await server.close();
|
||||
}
|
||||
@@ -82,6 +82,25 @@ try {
|
||||
),
|
||||
true,
|
||||
);
|
||||
await page.evaluate(() => {
|
||||
window.savedClipboard = navigator.clipboard;
|
||||
Object.defineProperty(navigator, 'clipboard', {
|
||||
configurable: true,
|
||||
value: undefined,
|
||||
});
|
||||
});
|
||||
await button('Copy key').click();
|
||||
assert.equal(
|
||||
await page.evaluate(() => window.savedClipboard.readText()),
|
||||
'MANUALSETUPTESTKEY',
|
||||
);
|
||||
assert.equal(await dialog().locator('textarea').count(), 0);
|
||||
await page.evaluate(() => {
|
||||
Object.defineProperty(navigator, 'clipboard', {
|
||||
configurable: true,
|
||||
value: window.savedClipboard,
|
||||
});
|
||||
});
|
||||
await button('Continue').click();
|
||||
const code = page.getByLabel('Authentication code', {
|
||||
exact: true,
|
||||
|
||||
Reference in New Issue
Block a user