Mirror of https://github.com/improsocial/impro An extensible Bluesky client for web impro.social
5

Configure Feed

Select the types of activity you want to include in your feed.

Hide unpin button when there's one feed remaining

Grace Kind (Jul 17, 2026, 2:47 PM -0500) 8864d4e2 6594bfaf

+67 -18
+1 -1
package.json
··· 1 1 { 2 2 "name": "impro", 3 - "version": "0.17.190", 3 + "version": "0.17.191", 4 4 "type": "module", 5 5 "scripts": { 6 6 "start": "rm -rf \"${BUILD_DIR:-build}\" && NODE_ENV=development eleventy --serve",
+1 -1
playwright.config.js
··· 22 22 webServer: { 23 23 command: "npm run build && PORT=8081 npm run serve:static", 24 24 url: "http://localhost:8081", 25 - reuseExistingServer: false, 25 + reuseExistingServer: true, 26 26 env: { PLAYWRIGHT: "1", BUILD_DIR: E2E_BUILD_DIR }, 27 27 }, 28 28 });
+27 -16
src/js/views/feeds.view.js
··· 51 51 state.$isSaving.set(true); 52 52 try { 53 53 await dataLayer.mutations.setPinnedItems(nextValues); 54 + showToast("Feeds updated!"); 54 55 } catch { 55 56 showToast("Couldn't save changes"); 56 57 return; ··· 70 71 state.$isEditing.set(false); 71 72 } 72 73 73 - function editControlsTemplate({ value, isSaving }) { 74 - return html`<button 75 - class="feeds-list-item-unpin-button" 76 - data-testid="feeds-list-item-unpin-button" 77 - aria-label="Unpin" 78 - ?disabled=${isSaving} 79 - @click=${(e) => { 80 - e.preventDefault(); 81 - e.stopPropagation(); 82 - state.$draftUnpinned.add(value); 83 - }} 84 - > 85 - ${pinIconTemplate({ filled: true })} 86 - </button> 74 + function editControlsTemplate({ value, isSaving, showUnpin }) { 75 + return html`${showUnpin 76 + ? html`<button 77 + class="feeds-list-item-unpin-button" 78 + data-testid="feeds-list-item-unpin-button" 79 + aria-label="Unpin" 80 + ?disabled=${isSaving} 81 + @click=${(e) => { 82 + e.preventDefault(); 83 + e.stopPropagation(); 84 + state.$draftUnpinned.add(value); 85 + }} 86 + > 87 + ${pinIconTemplate({ filled: true })} 88 + </button>` 89 + : ""} 87 90 <button 88 91 class="feeds-list-item-drag-handle" 89 92 data-testid="feeds-list-item-drag-handle" ··· 94 97 </button>`; 95 98 } 96 99 97 - function rowTemplate({ item, currentUser, isEditing, isSaving }) { 100 + function rowTemplate({ 101 + item, 102 + currentUser, 103 + isEditing, 104 + isSaving, 105 + showUnpin, 106 + }) { 98 107 const value = valueForPinnedItem(item); 99 108 const editControls = isEditing 100 - ? editControlsTemplate({ value, isSaving }) 109 + ? editControlsTemplate({ value, isSaving, showUnpin }) 101 110 : ""; 102 111 if (item.type === "timeline") { 103 112 return html`<div ··· 200 209 } 201 210 202 211 const canEdit = (pinnedItems?.length ?? 0) >= 2; 212 + const showUnpin = (orderedItems?.length ?? 0) >= 2; 203 213 204 214 render( 205 215 html`<div id="feeds-view"> ··· 254 264 currentUser, 255 265 isEditing, 256 266 isSaving, 267 + showUnpin, 257 268 }), 258 269 ) 259 270 : Array.from({ length: 5 }).map(() =>
+38
tests/e2e/specs/views/feeds.view.test.js
··· 268 268 .map((it) => it.value); 269 269 expect(pinnedValues).toEqual([feedA.uri, feedB.uri, "following"]); 270 270 271 + await expect(page.locator('[data-testid="toast"]')).toBeVisible(); 272 + 271 273 // After saving, the header reverts to the gear button. 272 274 await expect( 273 275 feedsView.locator('[data-testid="feeds-edit-button"]'), ··· 453 455 .allTextContents(); 454 456 expect(titlesAfterUnpin).toEqual(["Following", "Feed B"]); 455 457 expect(sawPut).toBe(false); 458 + }); 459 + 460 + test("the unpin button disappears when only one feed remains", async ({ 461 + page, 462 + }) => { 463 + const mockServer = new MockServer(); 464 + const feedA = createFeedGenerator({ 465 + uri: "at://did:plc:a/app.bsky.feed.generator/a", 466 + displayName: "Feed A", 467 + creatorHandle: "creator-a.bsky.social", 468 + }); 469 + mockServer.addFeedGenerators([feedA]); 470 + mockServer.setPinnedFeeds([feedA.uri]); 471 + await mockServer.setup(page); 472 + await login(page); 473 + await page.goto("/feeds"); 474 + 475 + const feedsView = page.locator("#feeds-view"); 476 + await expect(feedsView.locator(".feeds-list-item")).toHaveCount(2, { 477 + timeout: 10000, 478 + }); 479 + 480 + await feedsView.locator('[data-testid="feeds-edit-button"]').click(); 481 + await expect( 482 + feedsView.locator('[data-testid="feeds-list-item-unpin-button"]'), 483 + ).toHaveCount(2); 484 + 485 + const feedARow = feedsView.locator(`[data-pinned-value="${feedA.uri}"]`); 486 + await feedARow 487 + .locator('[data-testid="feeds-list-item-unpin-button"]') 488 + .click(); 489 + 490 + await expect(feedsView.locator(".feeds-list-item")).toHaveCount(1); 491 + await expect( 492 + feedsView.locator('[data-testid="feeds-list-item-unpin-button"]'), 493 + ).toHaveCount(0); 456 494 }); 457 495 458 496 test("unpin + Save persists via putPreferences and exits edit mode", async ({