From 870b2461872eea45998df2482998b46f43d4582e Mon Sep 17 00:00:00 2001 From: Jessica Deen Date: Mon, 17 Aug 2026 16:48:38 -0700 Subject: [PATCH 1/2] Clarify README banner markdown instructions Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/components/ReadmeBanner.tsx | 28 ++++++++++++---- tests/health.spec.ts | 58 +++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 7 deletions(-) diff --git a/src/components/ReadmeBanner.tsx b/src/components/ReadmeBanner.tsx index 53786aa..5250a01 100644 --- a/src/components/ReadmeBanner.tsx +++ b/src/components/ReadmeBanner.tsx @@ -68,6 +68,24 @@ const LANGUAGE_COLORS: Record = { Svelte: "#ff3e00", }; +const README_BANNER_FILENAME = "banner.png"; + +const getReadmeBannerMarkdown = (username: string) => + `![${username}'s GitHub Banner](https://raw.githubusercontent.com/${username}/${username}/main/${README_BANNER_FILENAME})`; + +const getReadmeBannerInstructions = (username: string, markdown: string) => + `✓ Markdown copied to clipboard! + +Next steps: +1. Download the banner and save it as ${README_BANNER_FILENAME}. +2. Upload ${README_BANNER_FILENAME} to the root of your ${username}/${username} profile repository. +3. Paste the copied Markdown in README.md. + +The Markdown points to /${README_BANNER_FILENAME} on the main branch: +${markdown} + +If you use a different filename or folder, update the Markdown path to match.`; + const ReadmeBanner = forwardRef( ( { user, artType, availableForHire, showWebsite, showJoinDate, showBio }, @@ -520,19 +538,15 @@ const ReadmeBanner = forwardRef( * Copy markdown code to clipboard */ const copyMarkdown = () => { - // You would typically host the image on GitHub or a CDN - // For now, we'll provide a template markdown that users can update - const markdown = `![${user.login}'s GitHub Banner](https://raw.githubusercontent.com/${user.login}/${user.login}/main/banner.png)`; + const markdown = getReadmeBannerMarkdown(user.login); navigator.clipboard .writeText(markdown) .then(() => { - alert( - "✓ Markdown copied to clipboard!\n\nUpload your banner.png to your profile repository and use this code in your README." - ); + alert(getReadmeBannerInstructions(user.login, markdown)); }) .catch(() => { - alert("Markdown code:\n\n" + markdown); + alert(getReadmeBannerInstructions(user.login, markdown)); }); }; diff --git a/tests/health.spec.ts b/tests/health.spec.ts index 132da82..c52ed6e 100644 --- a/tests/health.spec.ts +++ b/tests/health.spec.ts @@ -7,3 +7,61 @@ test('octocanvas homepage responds with expected title text', async ({ request } expect(body).toContain('OCTOCANVAS'); expect(body).toContain('Collectibles'); }); + +test('README banner markdown popup explains where to place the image', async ({ page }) => { + await page.route('https://api.github.com/users/octocat', async (route) => { + await route.fulfill({ + contentType: 'application/json', + body: JSON.stringify({ + login: 'octocat', + avatar_url: 'https://avatars.githubusercontent.com/u/583231?v=4', + name: 'The Octocat', + followers: 1234, + public_repos: 8, + bio: 'GitHub mascot', + created_at: '2011-01-25T18:44:36Z', + company: '@github', + location: 'San Francisco', + blog: 'github.blog', + }), + }); + }); + + await page.route('https://github.com/octocat.contribs', async (route) => { + await route.fulfill({ + contentType: 'application/json', + body: JSON.stringify({ + total_contributions: 42, + weeks: [], + }), + }); + }); + + await page.route('https://api.github.com/users/octocat/repos?*', async (route) => { + await route.fulfill({ + contentType: 'application/json', + body: JSON.stringify([ + { stargazers_count: 10, forks_count: 2, language: 'TypeScript' }, + ]), + }); + }); + + await page.goto('/'); + const usernameInput = page.locator('#github-handle'); + await usernameInput.click(); + await usernameInput.pressSequentially('octocat'); + await expect(usernameInput).toHaveValue('octocat'); + await page.getByRole('button', { name: 'Generate' }).click(); + await page.getByRole('tab', { name: 'README Banner' }).click(); + + const dialogPromise = page.waitForEvent('dialog'); + await page.getByRole('button', { name: 'Copy Markdown' }).click(); + const dialog = await dialogPromise; + + expect(dialog.message()).toContain('Download the banner and save it as banner.png.'); + expect(dialog.message()).toContain('Upload banner.png to the root of your octocat/octocat profile repository.'); + expect(dialog.message()).toContain('The Markdown points to /banner.png on the main branch:'); + expect(dialog.message()).toContain('https://raw.githubusercontent.com/octocat/octocat/main/banner.png'); + + await dialog.dismiss(); +}); From 9a43e0626ab93967b6c648599eecc5d227099e20 Mon Sep 17 00:00:00 2001 From: Jessica Deen Date: Mon, 17 Aug 2026 18:21:26 -0700 Subject: [PATCH 2/2] Handle README banner markdown copy failures Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 631b4b4f-d5c8-43fa-a88d-517f12e2ae4b --- src/components/ReadmeBanner.tsx | 14 +++++--- tests/health.spec.ts | 60 +++++++++++++++++++++++++++------ 2 files changed, 58 insertions(+), 16 deletions(-) diff --git a/src/components/ReadmeBanner.tsx b/src/components/ReadmeBanner.tsx index 5250a01..7601fac 100644 --- a/src/components/ReadmeBanner.tsx +++ b/src/components/ReadmeBanner.tsx @@ -73,13 +73,17 @@ const README_BANNER_FILENAME = "banner.png"; const getReadmeBannerMarkdown = (username: string) => `![${username}'s GitHub Banner](https://raw.githubusercontent.com/${username}/${username}/main/${README_BANNER_FILENAME})`; -const getReadmeBannerInstructions = (username: string, markdown: string) => - `✓ Markdown copied to clipboard! +const getReadmeBannerInstructions = ( + username: string, + markdown: string, + copied: boolean +) => + `${copied ? "✓ Markdown copied to clipboard!" : "Markdown could not be copied automatically."} Next steps: 1. Download the banner and save it as ${README_BANNER_FILENAME}. 2. Upload ${README_BANNER_FILENAME} to the root of your ${username}/${username} profile repository. -3. Paste the copied Markdown in README.md. +3. ${copied ? "Paste the copied Markdown in README.md." : "Copy the Markdown below into README.md."} The Markdown points to /${README_BANNER_FILENAME} on the main branch: ${markdown} @@ -543,10 +547,10 @@ const ReadmeBanner = forwardRef( navigator.clipboard .writeText(markdown) .then(() => { - alert(getReadmeBannerInstructions(user.login, markdown)); + alert(getReadmeBannerInstructions(user.login, markdown, true)); }) .catch(() => { - alert(getReadmeBannerInstructions(user.login, markdown)); + alert(getReadmeBannerInstructions(user.login, markdown, false)); }); }; diff --git a/tests/health.spec.ts b/tests/health.spec.ts index c52ed6e..28e8f5f 100644 --- a/tests/health.spec.ts +++ b/tests/health.spec.ts @@ -1,14 +1,6 @@ -import { test, expect } from '@playwright/test'; +import { test, expect, type Page } from '@playwright/test'; -test('octocanvas homepage responds with expected title text', async ({ request }) => { - const res = await request.get('/'); - expect(res.status(), 'status should be 200').toBe(200); - const body = await res.text(); - expect(body).toContain('OCTOCANVAS'); - expect(body).toContain('Collectibles'); -}); - -test('README banner markdown popup explains where to place the image', async ({ page }) => { +async function mockOctocatProfile(page: Page) { await page.route('https://api.github.com/users/octocat', async (route) => { await route.fulfill({ contentType: 'application/json', @@ -45,7 +37,9 @@ test('README banner markdown popup explains where to place the image', async ({ ]), }); }); +} +async function openOctocatReadmeBanner(page: Page) { await page.goto('/'); const usernameInput = page.locator('#github-handle'); await usernameInput.click(); @@ -53,15 +47,59 @@ test('README banner markdown popup explains where to place the image', async ({ await expect(usernameInput).toHaveValue('octocat'); await page.getByRole('button', { name: 'Generate' }).click(); await page.getByRole('tab', { name: 'README Banner' }).click(); +} +async function copyMarkdownDialog(page: Page) { const dialogPromise = page.waitForEvent('dialog'); - await page.getByRole('button', { name: 'Copy Markdown' }).click(); + const copyMarkdownPromise = page.getByRole('button', { name: 'Copy Markdown' }).evaluate((button: HTMLButtonElement) => { + button.click(); + }); const dialog = await dialogPromise; + return { dialog, copyMarkdownPromise }; +} + +test('octocanvas homepage responds with expected title text', async ({ request }) => { + const res = await request.get('/'); + expect(res.status(), 'status should be 200').toBe(200); + const body = await res.text(); + expect(body).toContain('OCTOCANVAS'); + expect(body).toContain('Collectibles'); +}); + +test('README banner markdown popup explains where to place the image', async ({ page }) => { + await mockOctocatProfile(page); + await openOctocatReadmeBanner(page); + const { dialog, copyMarkdownPromise } = await copyMarkdownDialog(page); + expect(dialog.message()).toContain('Download the banner and save it as banner.png.'); expect(dialog.message()).toContain('Upload banner.png to the root of your octocat/octocat profile repository.'); expect(dialog.message()).toContain('The Markdown points to /banner.png on the main branch:'); expect(dialog.message()).toContain('https://raw.githubusercontent.com/octocat/octocat/main/banner.png'); await dialog.dismiss(); + await copyMarkdownPromise; +}); + +test('README banner markdown popup exposes markdown when clipboard copy fails', async ({ page }) => { + await page.addInitScript(() => { + Object.defineProperty(Navigator.prototype, 'clipboard', { + value: { + writeText: () => Promise.reject(new Error('Clipboard denied')), + }, + configurable: true, + }); + }); + + await mockOctocatProfile(page); + await openOctocatReadmeBanner(page); + const { dialog, copyMarkdownPromise } = await copyMarkdownDialog(page); + + expect(dialog.message()).toContain('Markdown could not be copied automatically.'); + expect(dialog.message()).toContain('Copy the Markdown below into README.md.'); + expect(dialog.message()).toContain('https://raw.githubusercontent.com/octocat/octocat/main/banner.png'); + expect(dialog.message()).not.toContain('Markdown copied to clipboard'); + + await dialog.dismiss(); + await copyMarkdownPromise; });