From 89ba968230d7e704b5754a43ef64fa7b91c6c84e Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Thu, 8 Oct 2026 06:14:29 -0700 Subject: [PATCH] ci: keep shared unit fixture out of package and browser jobs (#26575) --- .oxlintrc.json | 28 ++++++++++++ .../check-changed-code-quality.test.mjs | 34 +++++++++++++- config/scripts/pr-code-change-scope.mjs | 11 ++++- config/scripts/pr-e2e-source-routing.mjs | 3 +- .../scripts/pr-unit-support-routing.test.mjs | 45 +++++++++++++++++++ 5 files changed, 117 insertions(+), 4 deletions(-) create mode 100644 config/scripts/pr-unit-support-routing.test.mjs diff --git a/.oxlintrc.json b/.oxlintrc.json index fec10ce318e..78dee1acd90 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -140,6 +140,34 @@ "unicorn/throw-new-error": "error" }, "overrides": [ + { + "files": ["**/*.{js,cjs,mjs,jsx,ts,tsx,mts,cts}"], + "excludeFiles": [ + "**/*.{test,spec}.{js,cjs,mjs,jsx,ts,tsx,mts,cts}", + "**/__tests__/**", + "src/renderer/src/runtime/web-runtime-browser-creation-placement-test-rig.ts" + ], + "rules": { + "no-restricted-imports": [ + "error", + { + "paths": [ + { + "name": "@linear/sdk", + "allowTypeImports": true, + "message": "Value-importing @linear/sdk hoists its ~2.6MB CJS bundle into the eager top-level require block and defeats the lazy loader. Use `import type` plus loadLinearSdk() from src/main/linear/linear-sdk.ts." + } + ], + "patterns": [ + { + "regex": "(?i)(?:^|[/\\\\])(?:web-session-tabs-sync-test-harness|web-runtime-browser-creation-placement-test-rig)(?:\\.[cm]?[jt]sx?)?(?:[?#].*)?$", + "message": "This module is unit-test support. Import the production web-session owner instead." + } + ] + } + ] + } + }, { "files": ["src/renderer/src/**/*.{ts,tsx}"], "rules": { diff --git a/config/scripts/check-changed-code-quality.test.mjs b/config/scripts/check-changed-code-quality.test.mjs index 783ee140adc..e1e93b3d53b 100644 --- a/config/scripts/check-changed-code-quality.test.mjs +++ b/config/scripts/check-changed-code-quality.test.mjs @@ -131,10 +131,10 @@ describe('unloaded plugin directive unused warning', () => { // Assembled so no line here is itself a directive the gate would scan. const directive = (rule) => `/* oxlint-disable ${rule} -- reason */` - const withFixture = (firstLine, assert) => { + const withFixture = (firstLine, assert, filename = 'fixture.ts') => { const directory = mkdtempSync(path.join(root, 'config', 'anti-slop-directive-test-')) try { - const file = path.join(directory, 'fixture.ts') + const file = path.join(directory, filename) writeFileSync(file, [firstLine, 'export const value = 1', ''].join('\n')) assert({ message: 'Unused oxlint-disable directive (no problems were reported).', @@ -186,6 +186,36 @@ describe('unloaded plugin directive unused warning', () => { return JSON.parse(result.stdout).diagnostics } + it.each([ + "import './web-session-tabs-sync-test-harness'", + "export * from '@/runtime/web-session-tabs-sync-test-harness'", + "void import('@renderer/runtime/web-session-tabs-sync-test-harness.ts')", + "import '../runtime/web-runtime-browser-creation-placement-test-rig'" + ])('rejects unit-support imports in production source: %s', (source) => { + withFixture(source, ({ filename }) => { + const diagnostics = scanFixture('code quality', filename).filter( + (diagnostic) => diagnostic.code === 'eslint(no-restricted-imports)' + ) + expect(diagnostics).toHaveLength(1) + }) + }) + + it.each(['fixture.test.ts', 'fixture.spec.ts'])( + 'allows unit-support imports from the existing test convention: %s', + (filename) => { + withFixture( + "import '@/runtime/web-session-tabs-sync-test-harness'", + (diagnostic) => { + const diagnostics = scanFixture('code quality', diagnostic.filename).filter( + (finding) => finding.code === 'eslint(no-restricted-imports)' + ) + expect(diagnostics).toEqual([]) + }, + filename + ) + } + ) + it('accepts a used Doctor directive only through its loaded scan', () => { const source = [ "import { useEffect, useState } from 'react'", diff --git a/config/scripts/pr-code-change-scope.mjs b/config/scripts/pr-code-change-scope.mjs index 841cc9bd8e7..50ac7fec598 100644 --- a/config/scripts/pr-code-change-scope.mjs +++ b/config/scripts/pr-code-change-scope.mjs @@ -564,8 +564,17 @@ function isProductBundlePath(file, extraPrefixes) { return matchesPrefix(file, extraPrefixes) } +// This shared fixture is consumed only by unit suites and their placement rig. +export function isUnitTestSupportSource(file) { + return file === 'src/renderer/src/runtime/web-session-tabs-sync-test-harness.ts' +} + function isTestFile(file) { - return /\.(?:test|spec)\.(?:js|cjs|mjs|ts|tsx)$/.test(file) || file.includes('/__tests__/') + return ( + isUnitTestSupportSource(file) || + /\.(?:test|spec)\.(?:js|cjs|mjs|ts|tsx)$/.test(file) || + file.includes('/__tests__/') + ) } function isDesktopIrrelevantPath(file) { diff --git a/config/scripts/pr-e2e-source-routing.mjs b/config/scripts/pr-e2e-source-routing.mjs index f080b28cb8d..07b65c947d5 100644 --- a/config/scripts/pr-e2e-source-routing.mjs +++ b/config/scripts/pr-e2e-source-routing.mjs @@ -1,7 +1,8 @@ import process from 'node:process' import { pathToFileURL } from 'node:url' +import { isUnitTestSupportSource } from './pr-code-change-scope.mjs' -const isProductSource = (file) => !/\.test\.tsx?$/.test(file) +const isProductSource = (file) => !/\.test\.tsx?$/.test(file) && !isUnitTestSupportSource(file) // Why config/patches: the xterm fork owns the helper textarea an input method attaches to, so a // patch edit can break composition without touching a file named "ime". diff --git a/config/scripts/pr-unit-support-routing.test.mjs b/config/scripts/pr-unit-support-routing.test.mjs new file mode 100644 index 00000000000..318b74bc780 --- /dev/null +++ b/config/scripts/pr-unit-support-routing.test.mjs @@ -0,0 +1,45 @@ +import { describe, expect, it } from 'vitest' +import { classifyPrJobs } from './pr-code-change-scope.mjs' +import { selectPrE2eSpecs } from './pr-e2e-source-routing.mjs' + +const harness = 'src/renderer/src/runtime/web-session-tabs-sync-test-harness.ts' + +describe('shared web-session unit support routing', () => { + it('keeps unit checks and lint while excluding packaging for the exact harness', () => { + const result = classifyPrJobs([harness]) + expect(result.should_run).toBe(true) + expect(result.static_analysis).toBe(true) + expect(result.typecheck).toBe(true) + expect(result.test).toBe(true) + expect(result.package).toBe(false) + expect(result.package_windows).toBe(false) + }) + + it('retains packaging for unknown helpers and mixed product changes', () => { + for (const file of [ + 'src/renderer/src/runtime/web-session-tabs-sync-next-test-harness.ts', + 'src/renderer/src/runtime/web-session-tabs-sync.ts' + ]) { + const result = classifyPrJobs([harness, file]) + expect(result.should_run, file).toBe(true) + expect(result.test, file).toBe(true) + expect(result.package, file).toBe(true) + expect(result.package_windows, file).toBe(true) + } + }) + + it('does not turn the exact unit harness into a two-app E2E', () => { + expect(selectPrE2eSpecs([harness])).toEqual([]) + }) + + it('retains E2E selection for unknown helpers, product changes and selected specs', () => { + const spec = 'tests/e2e/paired-client-hosted-browser-restart-survival.spec.ts' + for (const file of [ + 'src/renderer/src/runtime/web-session-tabs-sync-next-test-harness.ts', + 'src/renderer/src/runtime/web-session-tabs-sync.ts', + spec + ]) { + expect(selectPrE2eSpecs([harness, file]), file).toContain(spec) + } + }) +})