Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings
This repository was archived by the owner on Aug 5, 2025. It is now read-only.

fix: typed characters to be entered correctly#202

Merged
dummdidumm merged 4 commits intosveltejs:mainfromtomoam:add-test
Feb 2, 2023
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 28 additions & 7 deletionscontent/tutorial/common/src/__client.js
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -63,13 +63,34 @@ window.addEventListener('message', async (e) => {
}
});

window.addEventListener('pointerdown', () => {
parent.postMessage(
{
type: 'pointerdown'
},
'*'
);
/**
* The iframe sometimes takes focus control in ways we can't prevent
* while the editor is focussed. Refocus the editor in these cases.
*/
window.addEventListener('focusin', (e) => {
/**
* This condition would only be `true` if the iframe took focus when loaded,
* and `false` in other cases, for example:
* - navigation inside the iframe - for example, if you click a link inside
* the iframe, the `focusin` event will be fired twice, the first time
* `e.target` will be its anchor, the second time `e.target` will be body,
* and `e.relatedTarget` will be its anchor (if `csr = false` in only the
* first `focusin` event will be fired)
* - an element such as input gets focus (either from inside or outside the
* iframe) - for example, if an input inside the iframe gets focus,
* `e.target` will be the input.
*/
if (
e.target.tagName === 'BODY' &&
!e.target.contains(e.relatedTarget)
) {
parent.postMessage(
{
type: 'iframe_took_focus'
},
'*'
);
}
});

function ping() {
Expand Down
2 changes: 2 additions & 0 deletionspackage.json
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,12 +5,14 @@
"dev": "vite dev",
"build": "node scripts/create-common-bundle && vite build && node scripts/create-middleware",
"preview": "vite preview",
"test": "playwright test",
"check": "svelte-check --tsconfig ./jsconfig.json",
"check:watch": "svelte-check --tsconfig ./jsconfig.json --watch",
"lint": "prettier --ignore-path .gitignore --check --plugin-search-dir=. .",
"format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. ."
},
"devDependencies": {
"@playwright/test": "^1.30.0",
"@sveltejs/adapter-auto": "1.0.0-next.90",
"@sveltejs/adapter-vercel": "1.0.0-next.85",
"@sveltejs/kit": "next",
Expand Down
10 changes: 10 additions & 0 deletionsplaywright.config.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
import type { PlaywrightTestConfig } from '@playwright/test';

const config: PlaywrightTestConfig = {
webServer: {
command: 'pnpm build && pnpm preview',
port: 4173
}
};

export default config;
23 changes: 20 additions & 3 deletionspnpm-lock.yaml
View file
Open in desktop

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.

21 changes: 12 additions & 9 deletionssrc/routes/tutorial/[slug]/Editor.svelte
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,6 +31,8 @@
let h = 0;

let preserve_editor_focus = false;
/** @type {any} */
let remove_focus_timeout;

onMount(() => {
let destroyed = false;
Expand DownExpand Up@@ -237,8 +239,8 @@
}
}}
on:message={(e) => {
if (e.data.type === 'pointerdown') {
preserve_editor_focus = false;
if (preserve_editor_focus &&e.data.type === 'iframe_took_focus') {
instance?.editor.focus();
}
}}
/>
Expand All@@ -256,15 +258,16 @@
}
}}
on:focusin={() => {
clearTimeout(remove_focus_timeout);
preserve_editor_focus = true;
}}
on:focusout={() => {
//Little timeout, because inner postMessage event might take a little
setTimeout(() => {
if (preserve_editor_focus) {
instance?.editor.focus();
}
},100);
on:focusout={(e) => {
//Heuristic: user did refocus themmselves if iframe_took_focus
// doesn't happen in the next few miliseconds. Needed
// because else navigations inside the iframe refocus the editor.
remove_focus_timeout = setTimeout(() => {
preserve_editor_focus = false;
},200);
}}
/>
</div>
Expand Down
130 changes: 130 additions & 0 deletionstests/focus_management.spec.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
import { expect, test, chromium } from '@playwright/test';

const chromium_flags = ['--enable-features=SharedArrayBuffer'];

const editor_selector = 'div.monaco-scrollable-element.editor-scrollable';
const editor_focus_selector = 'textarea.inputarea.monaco-mouse-cursor-text';
const iframe_selector = 'iframe[src*="webcontainer.io/"]';

test('focus management: the editor keeps focus when iframe is loaded', async () => {
const context = await chromium.launchPersistentContext('', { args: chromium_flags });
const page = context.pages()[0];
await page.bringToFront();

await page.goto('/tutorial/your-first-component');

// first, focus the editor before the iframe is loaded
await page.locator(editor_selector).click({ delay: 1000 });

// at this time, expect focus to be on the editor
await expect(page.locator(editor_focus_selector)).toBeFocused();

// wait for the iframe to load
await page.frameLocator(iframe_selector).getByText('Hello world!').waitFor();

// wait a little, because there may be a script that manipulates focus
await page.waitForTimeout(1000);

// expect focus to be on the editor
await expect(page.locator(editor_focus_selector)).toBeFocused();

await context.close();
});

test('focus management: input inside the iframe gets focus when clicking it', async () => {
const context = await chromium.launchPersistentContext('', { args: chromium_flags });
const page = context.pages()[0];
await page.bringToFront();

await page.goto('/tutorial/named-form-actions');

const iframe = page.frameLocator(iframe_selector);

// wait for the iframe to load
await iframe.getByText('todos').waitFor();

// first, focus the editor
await page.locator(editor_selector).click({ delay: 1000 });
await expect(page.locator(editor_focus_selector)).toBeFocused();

// then, click a input in the iframe
const input = iframe.locator('input[name="description"]');
await input.click({ delay: 500 });

// wait a little, because there may be a script that manipulates focus
await page.waitForTimeout(1000);

// expect focus to be on the input in the iframe, not the editor.
await expect(input).toBeFocused();
await expect(page.locator(editor_focus_selector)).not.toBeFocused();

await context.close();
});

test('focus management: body inside the iframe gets focus when clicking a link inside the iframe', async () => {
const context = await chromium.launchPersistentContext('', { args: chromium_flags });
const page = context.pages()[0];
await page.bringToFront();

await page.goto('/tutorial/layouts');

const iframe = page.frameLocator(iframe_selector);

// wait for the iframe to load
await iframe.getByText('this is the home page.').waitFor();

// first, focus the editor
await page.locator(editor_selector).click({ delay: 1000 });
await expect(page.locator(editor_focus_selector)).toBeFocused();

// then, click a link in the iframe
await iframe.locator('a[href="/about"]').click({ delay: 500 });

// wait for navigation
await iframe.getByText('this is the about page.').waitFor();

// wait a little, because there may be a script that manipulates focus
await page.waitForTimeout(1000);

// expect focus to be on body in the iframe, not the editor.
await expect(iframe.locator('body')).toBeFocused();

await context.close();
});

test('focus management: the editor keeps focus while typing', async () => {
const context = await chromium.launchPersistentContext('', { args: chromium_flags });
const page = context.pages()[0];
await page.bringToFront();

await page.goto('/tutorial/your-first-component');

// wait for the iframe to load
await page.frameLocator(iframe_selector).getByText('Hello world!').waitFor();

// first, write script tag
const code = '<script>\n\n</script>\n';
await page.locator(editor_focus_selector).fill(code);

// move the cursor into the script tag
await page.keyboard.press('PageUp', { delay: 500 });
await page.keyboard.press('ArrowDown', { delay: 500 });

// wait a little because the above operation is flaky
await page.waitForTimeout(500);

// type the code as a person would do it manually
await page.keyboard.type(`export let data;`, { delay: 100 });

// wait a little, because there may be a script that manipulates focus
await page.waitForTimeout(1000);

// get code from DOM, then replace nbsp with normal space
const received = (await page.locator(editor_selector).innerText()).replace(/\u00a0/g, ' ');

const expected = '<script>\n export let data;\n</script>\n<h1>Hello world!</h1>';

expect(received).toBe(expected);

await context.close();
});

[8]ページ先頭

©2009-2025 Movatter.jp