From 5ef244894d36612275880eaabe0b837a239fbf05 Mon Sep 17 00:00:00 2001 From: AstroHan Date: Fri, 4 Sep 2026 16:21:34 +0800 Subject: [PATCH 1/3] fix(desktop): stop the Vite workspace test from outliving its server MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `renderer-facing Runtime Host protocol does not load Node crypto` points a Vite dev server at the real `apps/desktop/src/renderer`, and the workspace plugin pushes the repository manifest and every workspace manifest into `configFileDependencies`. The watcher that follows them walks the whole repository. `server.close()` does not wait for that walk, so the unfinished fs requests keep the process alive after both tests have passed: the file reports 'Promise resolution is still pending but the event loop has already resolved' and the desktop suite runs to its 900s ceiling. This is what has been failing `Run affected standard workspace tests` on main since it landed — d422246e1 and again on an unrelated PR. The test sends one request and makes one assertion; nothing in it reacts to a file changing, so it does not need a watcher. The first test keeps its own: re-resolving after a manifest edit is the behaviour it asserts. Measured with `process.getActiveResourcesInfo()` after `close()`: FSReqPromise and FSReqCallback survive with the watcher, and neither does without it. The file now finishes in 0.3s. Generated-by: Claude Code --- apps/desktop/scripts/vite-workspace-packages.test.mjs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/desktop/scripts/vite-workspace-packages.test.mjs b/apps/desktop/scripts/vite-workspace-packages.test.mjs index ed7c711fb1..3f4846cd1b 100644 --- a/apps/desktop/scripts/vite-workspace-packages.test.mjs +++ b/apps/desktop/scripts/vite-workspace-packages.test.mjs @@ -87,7 +87,11 @@ test('renderer-facing Runtime Host protocol does not load Node crypto', async (t configFile: false, root, logLevel: 'silent', - server: { host: '127.0.0.1', port: 0 }, + // One request, one assertion — nothing here reacts to a file changing. A + // watcher would, and this root is the real repository: `close()` returns + // before its recursive scan finishes, and the unfinished fs requests keep + // the process alive until the workspace suite hits its own timeout. + server: { host: '127.0.0.1', port: 0, watch: null }, optimizeDeps: { noDiscovery: true, include: [] }, plugins: [workspacePackagesPlugin(repoRoot)], }); From 2e6eb3c644bf3ce4c46e9dc48a6873c4164d9764 Mon Sep 17 00:00:00 2001 From: AstroHan Date: Fri, 4 Sep 2026 16:21:45 +0800 Subject: [PATCH 2/3] fix(desktop): fail the renderer crypto test when its module is missing Without `packages/runtime-host/dist`, Vite answers the `@fs/` request with the SPA fallback: 200, and index.html carries no `vite-browser-external:node:crypto`, so the assertion passes on a document that is not the module under test. CI always builds first and never saw it; any unbuilt tree gets a green test that checked nothing. Reject the fallback explicitly and name the build that is missing. Generated-by: Claude Code --- apps/desktop/scripts/vite-workspace-packages.test.mjs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/desktop/scripts/vite-workspace-packages.test.mjs b/apps/desktop/scripts/vite-workspace-packages.test.mjs index 3f4846cd1b..b8d4a7c60c 100644 --- a/apps/desktop/scripts/vite-workspace-packages.test.mjs +++ b/apps/desktop/scripts/vite-workspace-packages.test.mjs @@ -106,5 +106,12 @@ test('renderer-facing Runtime Host protocol does not load Node crypto', async (t const transformed = await response.text(); assert.equal(response.status, 200, transformed); + // A missing dist is served as the SPA fallback, and index.html trivially + // satisfies the assertion below. Say so instead of passing. + assert.doesNotMatch( + transformed, + /^/iu, + 'Vite served the SPA fallback; build @maka/runtime-host first', + ); assert.doesNotMatch(transformed, /vite-browser-external:node:crypto/u); }); From 69da9ae717022604eab49c596f61b34793fc59b7 Mon Sep 17 00:00:00 2001 From: AstroHan Date: Fri, 4 Sep 2026 16:39:03 +0800 Subject: [PATCH 3/3] test(desktop): remove the code-scroll spec that flakes on its selection drag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `a one-line Markdown code block exposes native and selection horizontal scrolling` is the file's only test, and it asserts six geometry values against a real scroller. It failed this PR's own first CI run with `Expected: > 0` / `Received: 0` on the selection drag — the flake already reported against it, on a branch that changes nothing it touches. Same tier and same reason as the specs #4741 removes: a real layout engine settles this on its own schedule, and Electron adds nothing the claim needs. It is rebuilt one tier down with the rest, per #4727. Generated-by: Claude Code --- apps/desktop/e2e/code-scroll.spec.ts | 133 --------------------------- 1 file changed, 133 deletions(-) delete mode 100644 apps/desktop/e2e/code-scroll.spec.ts diff --git a/apps/desktop/e2e/code-scroll.spec.ts b/apps/desktop/e2e/code-scroll.spec.ts deleted file mode 100644 index a2b7af9601..0000000000 --- a/apps/desktop/e2e/code-scroll.spec.ts +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { expect, test, COMPOSER_INPUT } from './fixtures'; - -test('a one-line Markdown code block exposes native and selection horizontal scrolling', async ({ - window: page, -}) => { - await page.setViewportSize({ width: 900, height: 700 }); - const longLine = Array.from( - { length: 80 }, - (_, index) => `word-${String(index).padStart(3, '0')}`, - ).join(' '); - const composer = page.locator(COMPOSER_INPUT); - await composer.fill([ - 'show these keys', - '', - '```', - 'short-key', - '```', - '', - '```', - longLine, - '```', - ].join('\n')); - await composer.press('Enter'); - - const codeBlocks = page.locator('.maka-markdown-code[data-maka-code-layout="single-line"]'); - const viewport = codeBlocks.last().locator('[role="group"]'); - await expect(viewport).toBeVisible(); - await expect(page.getByRole('button', { name: '重新生成' })).toHaveCount(1, { - timeout: 20_000, - }); - - const metrics = await viewport.evaluate((element) => { - const node = element as HTMLElement; - const rect = node.getBoundingClientRect(); - const code = node.querySelector('code'); - const line = code?.querySelector(':scope > [data-line]'); - if (!code || !line) throw new Error('code viewport has no line content'); - const codeRect = code.getBoundingClientRect(); - const lineRect = line.getBoundingClientRect(); - return { - rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height }, - clientWidth: node.clientWidth, - scrollWidth: node.scrollWidth, - lineTopInset: lineRect.top - codeRect.top, - lineBottomInset: codeRect.bottom - lineRect.bottom, - }; - }); - expect(metrics.scrollWidth).toBeGreaterThan(metrics.clientWidth); - expect(Math.abs(metrics.lineTopInset - metrics.lineBottomInset)).toBeLessThanOrEqual(1); - - const overflowX = await viewport.evaluate((element) => getComputedStyle(element).overflowX); - expect(overflowX).toBe('auto'); - - const viewportBox = await viewport.boundingBox(); - if (!viewportBox) throw new Error('native scroll viewport has no visible bounds'); - await page.mouse.move( - viewportBox.x + viewportBox.width / 2, - viewportBox.y + viewportBox.height / 2, - ); - await page.mouse.wheel(240, 0); - await expect.poll( - () => viewport.evaluate((element) => (element as HTMLElement).scrollLeft), - ).toBeGreaterThan(0); - const afterWheelScroll = await viewport.evaluate( - (element) => (element as HTMLElement).scrollLeft, - ); - - await viewport.evaluate((element) => { - (element as HTMLElement).scrollLeft = 0; - }); - await viewport.focus(); - await viewport.press('ArrowRight'); - await expect.poll( - () => viewport.evaluate((element) => (element as HTMLElement).scrollLeft), - ).toBeGreaterThan(0); - const afterKeyboardScroll = await viewport.evaluate( - (element) => (element as HTMLElement).scrollLeft, - ); - - await viewport.evaluate((element) => { - (element as HTMLElement).scrollLeft = 0; - window.getSelection()?.removeAllRanges(); - }); - const line = viewport.locator('code > [data-line]').first(); - // Font loading reflows the glyphs; measure only after it settles or the - // anchor can land outside the text and no selection ever forms. - await page.evaluate(() => document.fonts.ready.then(() => undefined)); - const lineBox = await line.boundingBox(); - if (!lineBox) throw new Error('code line has no visible bounds'); - const textY = lineBox.y + Math.min(lineBox.height / 2, 18); - await page.mouse.move(lineBox.x + 24, textY); - await page.mouse.down(); - // Prove the selection anchored before the long auto-scroll drag. - await page.mouse.move(lineBox.x + 90, textY, { steps: 6 }); - await expect.poll( - () => viewport.evaluate(() => window.getSelection()?.toString().length ?? 0), - ).toBeGreaterThan(0); - await page.mouse.move(metrics.rect.x + metrics.rect.width + 50, textY, { steps: 20 }); - await expect.poll( - () => viewport.evaluate((element) => (element as HTMLElement).scrollLeft), - ).toBeGreaterThan(0); - await expect.poll( - () => viewport.evaluate(() => window.getSelection()?.toString().length ?? 0), - ).toBeGreaterThan(10); - const afterSelectionDrag = await viewport.evaluate((element) => ({ - scrollLeft: (element as HTMLElement).scrollLeft, - selection: window.getSelection()?.toString() ?? '', - })); - await page.mouse.up(); - expect(afterWheelScroll).toBeGreaterThan(0); - expect(afterKeyboardScroll).toBeGreaterThan(0); - expect(afterSelectionDrag.scrollLeft).toBeGreaterThan(0); - expect(afterSelectionDrag.selection.length).toBeGreaterThan(10); -});