|
| 1 | +// NOT ported from upstream — upstream's extension predates the rstack bridge. |
| 2 | +// This suite covers the bridged-project path end to end: a folder whose only |
| 3 | +// test signal is `rstack.config.ts` (the `e2e/fixtures/rstack` fixture, shared |
| 4 | +// with the `vscode` slice) must get a synthesized project driven |
| 5 | +// through rstack's shipped shim, show the same node-less tree a native root |
| 6 | +// config gets, and actually run its tests through the worker. |
| 7 | +// |
| 8 | +// The fixture folder is added as a second workspace folder and removed again |
| 9 | +// in teardown: `suite/index.ts` collects `*.test.js` sorted, so this suite runs |
| 10 | +// *first*, and every suite after it (`index`, `progress`, ...) asserts on the |
| 11 | +// unwrapped single-folder tree the run starts with. |
| 12 | +// |
| 13 | +// Adding a folder also flips the tree into its wrapped layout, so the probes |
| 14 | +// below re-resolve the live controller through `currentRstestExports()` — a |
| 15 | +// detection change can deregister and re-register the stack, which publishes a |
| 16 | +// fresh `TestController` (same reason as `workspace.test.ts`). |
| 17 | +import assert from 'node:assert'; |
| 18 | +import path from 'node:path'; |
| 19 | +import vscode from 'vscode'; |
| 20 | +import { |
| 21 | + createCollectingMockRun, |
| 22 | + currentRstestExports, |
| 23 | + FIXTURES_ROOT, |
| 24 | + getRstestExports, |
| 25 | + getTestItemByLabels, |
| 26 | + toLabelTree, |
| 27 | + waitFor, |
| 28 | +} from './helpers'; |
| 29 | + |
| 30 | +/** `<repo>/e2e/fixtures/rstack` — the rstack-cli fixture, no tool-native config. */ |
| 31 | +const RSTACK_FIXTURE = path.resolve(FIXTURES_ROOT, '../../fixtures/rstack'); |
| 32 | +const RSTACK_FIXTURE_URI = vscode.Uri.file(RSTACK_FIXTURE); |
| 33 | + |
| 34 | +const WORKSPACE_1_FILES = [ |
| 35 | + { label: 'each.test.ts' }, |
| 36 | + { label: 'foo.test.ts' }, |
| 37 | + { label: 'index.test.ts' }, |
| 38 | + { label: 'jsFile.spec.js' }, |
| 39 | + { label: 'jsxFile.test.jsx' }, |
| 40 | + { label: 'progress.test.ts' }, |
| 41 | + { label: 'tsxFile.test.tsx' }, |
| 42 | +]; |
| 43 | + |
| 44 | +suite('Rstack bridge suite', () => { |
| 45 | + suiteSetup(async () => { |
| 46 | + await getRstestExports(); |
| 47 | + const added = vscode.workspace.updateWorkspaceFolders( |
| 48 | + vscode.workspace.workspaceFolders?.length || 0, |
| 49 | + 0, |
| 50 | + { uri: RSTACK_FIXTURE_URI }, |
| 51 | + ); |
| 52 | + assert.ok(added, 'adding the rstack fixture folder should be accepted'); |
| 53 | + }); |
| 54 | + |
| 55 | + suiteTeardown(async () => { |
| 56 | + // Compare `uri.toString()`, not `fsPath`: `fsPath` lower-cases the Windows |
| 57 | + // drive letter while `path.resolve` keeps it as-is, so a raw string |
| 58 | + // compare can miss on Windows — and a missed removal here would leak the |
| 59 | + // folder into every later suite. |
| 60 | + const index = vscode.workspace.workspaceFolders?.findIndex( |
| 61 | + (folder) => folder.uri.toString() === RSTACK_FIXTURE_URI.toString(), |
| 62 | + ); |
| 63 | + assert.ok(index !== undefined && index >= 0); |
| 64 | + const removed = vscode.workspace.updateWorkspaceFolders(index, 1); |
| 65 | + assert.ok(removed, 'removing the rstack fixture folder should be accepted'); |
| 66 | + // Later suites assert on the unwrapped single-folder tree; leave only |
| 67 | + // after the controller has actually settled back into it. |
| 68 | + await waitFor(() => { |
| 69 | + const testController = currentRstestExports().testController; |
| 70 | + assert.deepStrictEqual(toLabelTree(testController.items, true), [ |
| 71 | + { label: 'test', children: WORKSPACE_1_FILES }, |
| 72 | + ]); |
| 73 | + }); |
| 74 | + }); |
| 75 | + |
| 76 | + test('discovers a bridged project from rstack.config.ts alone', async () => { |
| 77 | + // Two detected folders → both wrapped in workspace nodes. The rstack |
| 78 | + // folder holds a single bridged project whose source config sits at the |
| 79 | + // folder root under a default name, so it gets the node-less layout — |
| 80 | + // structurally identical to workspace-1's native root config. This is the |
| 81 | + // first suite this slice runs in a cold Electron, so the probe pays |
| 82 | + // workspace-1's discovery AND the bridged project's first worker spawn |
| 83 | + // (User Node, shim + `loadRstackConfig()`, Rstest/Rspack init) — hence |
| 84 | + // the extended budget (the mocha timeout is 120s). |
| 85 | + await waitFor( |
| 86 | + () => { |
| 87 | + const testController = currentRstestExports().testController; |
| 88 | + assert.deepStrictEqual(toLabelTree(testController.items, true), [ |
| 89 | + { |
| 90 | + label: 'rstack', |
| 91 | + children: [ |
| 92 | + { |
| 93 | + label: 'tests', |
| 94 | + children: [{ label: 'basic.test.ts' }], |
| 95 | + }, |
| 96 | + ], |
| 97 | + }, |
| 98 | + { |
| 99 | + label: 'workspace-1', |
| 100 | + children: [{ label: 'test', children: WORKSPACE_1_FILES }], |
| 101 | + }, |
| 102 | + ]); |
| 103 | + }, |
| 104 | + { timeoutMs: 60_000 }, |
| 105 | + ); |
| 106 | + |
| 107 | + // Test-case level (AST collection) inside the bridged project. |
| 108 | + await waitFor(() => { |
| 109 | + const testController = currentRstestExports().testController; |
| 110 | + const file = getTestItemByLabels(testController.items, [ |
| 111 | + 'rstack', |
| 112 | + 'tests', |
| 113 | + 'basic.test.ts', |
| 114 | + ]); |
| 115 | + assert.deepStrictEqual(toLabelTree(file.children), [ |
| 116 | + { label: 'trims a string' }, |
| 117 | + ]); |
| 118 | + }); |
| 119 | + }); |
| 120 | + |
| 121 | + test('runs bridged tests through the rstack config shim', async () => { |
| 122 | + const collecting = createCollectingMockRun(); |
| 123 | + |
| 124 | + // Resolve the exports and the item together: holding an instance from |
| 125 | + // before the poll would keep a controller a re-registration had replaced. |
| 126 | + const { rstestInstance, item } = await waitFor(() => { |
| 127 | + const rstestInstance = currentRstestExports(); |
| 128 | + return { |
| 129 | + rstestInstance, |
| 130 | + item: getTestItemByLabels(rstestInstance.testController.items, [ |
| 131 | + 'rstack', |
| 132 | + 'tests', |
| 133 | + 'basic.test.ts', |
| 134 | + ]), |
| 135 | + }; |
| 136 | + }); |
| 137 | + |
| 138 | + rstestInstance.startTestRun( |
| 139 | + new vscode.TestRunRequest([item], undefined, rstestInstance.runProfile), |
| 140 | + new vscode.CancellationTokenSource().token, |
| 141 | + false, |
| 142 | + collecting.createMockRun, |
| 143 | + ); |
| 144 | + await collecting.ended; |
| 145 | + |
| 146 | + assert.equal(collecting.failedMessages.length, 0); |
| 147 | + // A file requested as a whole reports twice: the case itself |
| 148 | + // (`onTestCaseResult`) and the file item, which only goes green when the |
| 149 | + // whole file passed (`onTestFileResult`). `progress.test.ts` never sees the |
| 150 | + // second one — its file always has failures. |
| 151 | + assert.deepStrictEqual( |
| 152 | + collecting.passedItems.map((passed) => passed.label).sort(), |
| 153 | + ['basic.test.ts', 'trims a string'], |
| 154 | + ); |
| 155 | + assert.match(collecting.output, /1 passed/); |
| 156 | + }); |
| 157 | +}); |
0 commit comments