| @@ -3,10 +3,14 @@ · 2 unmodified lines |
| 3 | 3 | // it reads as the header value being built ("Authorization: Bearer ▏token"); |
| 4 | 4 | // - the "Add authentication method" editor offers placement presets; |
| 5 | 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 | 10 | // Video is the artifact. |
| 8 | 11 | import { randomBytes } from "node:crypto"; |
| 9 | 12 | |
| 13 | +import { expect } from "@effect/vitest"; |
| 10 | 14 | import { Effect } from "effect"; |
| 11 | 15 | import { composePluginApi } from "@executor-js/api/server"; |
| 12 | 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 | 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 | 34 | const bearerSpec = (): string => |
| 21 | 35 | JSON.stringify({ |
| 22 | 36 | openapi: "3.0.3", |
| @@ -75,6 +89,35 @@ scenario( · 52 unmodified lines |
| 75 | 89 | await page.getByPlaceholder("Bearer ").first().fill("Bearer "); |
| 76 | 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 | 123 | apiClient.openapi |