| @@ -3,10 +3,14 @@ · 2 unmodified lines |
| 3 | // it reads as the header value being built ("Authorization: Bearer ▏token"); | 3 | // it reads as the header value being built ("Authorization: Bearer ▏token"); |
| 4 | // - the "Add authentication method" editor offers placement presets; | 4 | // - the "Add authentication method" editor offers placement presets; |
| 5 | // - a prefix with no trailing space (sent joined to the value) warns, and the | 5 | // - a prefix with no trailing space (sent joined to the value) warns, and the |
| 6 | // warning clears once the space is restored. | 6 | // warning clears once the space is restored; |
| | 7 | // - a very long pasted key wraps inside the dialog instead of stretching it |
| | 8 | // past the viewport (the preview breaks mid-token, the merged affix |
| | 9 | // truncates). |
| 7 | // Video is the artifact. | 10 | // Video is the artifact. |
| 8 | import { randomBytes } from "node:crypto"; | 11 | import { randomBytes } from "node:crypto"; |
| 9 | | 12 | |
| | 13 | import { expect } from "@effect/vitest"; |
| 10 | import { Effect } from "effect"; | 14 | import { Effect } from "effect"; |
| 11 | import { composePluginApi } from "@executor-js/api/server"; | 15 | import { composePluginApi } from "@executor-js/api/server"; |
| 12 | import { openApiHttpPlugin } from "@executor-js/plugin-openapi/api"; | 16 | import { openApiHttpPlugin } from "@executor-js/plugin-openapi/api"; |
| @@ -17,6 +21,16 @@ import { Api, Browser, Target } from "../src/services"; · 4 unmodified lines |
| 17 | | 21 | |
| 18 | const api = composePluginApi([openApiHttpPlugin()] as const); | 22 | const api = composePluginApi([openApiHttpPlugin()] as const); |
| 19 | | 23 | |
| | 24 | /** Horizontal overflow of the open dialog in px (0 = fits). Infinity when no |
| | 25 | * dialog is open, so an assertion against it always fails loudly. */ |
| | 26 | const dialogOverflow = (page: { |
| | 27 | readonly evaluate: <T>(fn: () => T) => Promise<T>; |
| | 28 | }): Promise<number> => |
| | 29 | page.evaluate(() => { |
| | 30 | const dialog = document.querySelector('[role="dialog"]'); |
| | 31 | return dialog === null ? Number.POSITIVE_INFINITY : dialog.scrollWidth - dialog.clientWidth; |
| | 32 | }); |
| | 33 | |
| 20 | const bearerSpec = (): string => | 34 | const bearerSpec = (): string => |
| 21 | JSON.stringify({ | 35 | JSON.stringify({ |
| 22 | openapi: "3.0.3", | 36 | openapi: "3.0.3", |
| @@ -75,6 +89,35 @@ scenario( · 52 unmodified lines |
| 75 | await page.getByPlaceholder("Bearer ").first().fill("Bearer "); | 89 | await page.getByPlaceholder("Bearer ").first().fill("Bearer "); |
| 76 | await page.getByText("Prefix has no trailing space").waitFor({ state: "detached" }); | 90 | await page.getByText("Prefix has no trailing space").waitFor({ state: "detached" }); |
| 77 | }); | 91 | }); |
| | 92 | |
| | 93 | await step("A long pasted key never widens the dialog", async () => { |
| | 94 | // A key mistakenly pasted into Prefix is the worst case: one |
| | 95 | // unbroken 700-char token rendered into the preview line. The |
| | 96 | // dialog must keep its width (the preview wraps mid-token). |
| | 97 | const longKey = `${"x".repeat(300)}Bearer${"x".repeat(400)}`; |
| | 98 | // The preview line renders only once the placement is named. |
| | 99 | await page.getByPlaceholder("Authorization").first().fill("Authorization"); |
| | 100 | await page.getByPlaceholder("Bearer ").first().fill(longKey); |
| | 101 | await page.getByText("Preview").waitFor(); |
| | 102 | // ≤ 1px: subpixel rounding can report a fractional scrollWidth. |
| | 103 | expect( |
| | 104 | await dialogOverflow(page), |
| | 105 | "the add-method dialog must not scroll horizontally", |
| | 106 | ).toBeLessThanOrEqual(1); |
| | 107 | }); |
| | 108 | |
| | 109 | await step("The merged affix truncates a long saved prefix", async () => { |
| | 110 | // Saving the method returns to the connect modal with the new |
| | 111 | // method selected; its credential field merges the (700-char) |
| | 112 | // prefix as the affix, which must truncate inside the field. |
| | 113 | await page.getByRole("button", { name: "Add method", exact: true }).click(); |
| | 114 | await page.getByRole("heading", { name: /Add connection/ }).waitFor(); |
| | 115 | await page.getByText("Authorization: x").first().waitFor(); |
| | 116 | expect( |
| | 117 | await dialogOverflow(page), |
| | 118 | "the connect modal must not scroll horizontally", |
| | 119 | ).toBeLessThanOrEqual(1); |
| | 120 | }); |
| 78 | }); | 121 | }); |
| 79 | }), | 122 | }), |
| 80 | apiClient.openapi | 123 | apiClient.openapi |