This repository has no description
0

Configure Feed

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

core / web / src / lib / components / repo / PierreDiff.stories.svelte
6.0 kB 191 lines
1<script module lang="ts"> 2 import { defineMeta, type StoryContext } from "@storybook/addon-svelte-csf"; 3 import { expect, waitFor } from "storybook/test"; 4 import { LineOp, type DiffFile } from "$lib/api/diff"; 5 import { toFileDiffMetadata } from "./fileDiff"; 6 import PierreDiff from "./PierreDiff.svelte"; 7 8 const context = (line: string) => ({ Op: LineOp.Context, Line: `${line}\n` }); 9 const deletion = (line: string) => ({ Op: LineOp.Delete, Line: `${line}\n` }); 10 const addition = (line: string) => ({ Op: LineOp.Add, Line: `${line}\n` }); 11 12 const files: DiffFile[] = [ 13 { 14 name: { old: "src/colors.ts", new: "src/colors.ts" }, 15 text_fragments: [ 16 { 17 OldPosition: 1, 18 OldLines: 4, 19 NewPosition: 1, 20 NewLines: 6, 21 Lines: [ 22 context("export const palette = {"), 23 deletion('\tbackground: "white",'), 24 deletion('\tforeground: "black",'), 25 addition('\tbackground: "oklch(0.98 0 0)",'), 26 addition('\tforeground: "oklch(0.2 0 0)",'), 27 context("};"), 28 addition(""), 29 addition('export const accent = "oklch(0.6 0.2 250)";') 30 ] 31 }, 32 { 33 OldPosition: 10, 34 OldLines: 3, 35 NewPosition: 11, 36 NewLines: 3, 37 Lines: [ 38 context("export const spacing = 4;"), 39 deletion("export const radius = 0;"), 40 addition("export const radius = 8;"), 41 context('export const shadow = "none";') 42 ] 43 } 44 ] 45 }, 46 { 47 name: { old: "src/old.ts", new: "src/renamed.ts" }, 48 is_rename: true, 49 text_fragments: [ 50 { 51 OldPosition: 1, 52 OldLines: 2, 53 NewPosition: 1, 54 NewLines: 2, 55 Lines: [ 56 deletion('export const name = "old";'), 57 addition('export const name = "renamed";'), 58 context("export default name;") 59 ] 60 } 61 ] 62 } 63 ]; 64 65 // the hunk starts deep in the file, the leading unmodified region collapses into a 66 // top separator 67 const offsetFiles: DiffFile[] = [ 68 { 69 name: { old: "src/deep.ts", new: "src/deep.ts" }, 70 text_fragments: [ 71 { 72 OldPosition: 20, 73 OldLines: 3, 74 NewPosition: 20, 75 NewLines: 3, 76 Lines: [ 77 context("export const spacing = 4;"), 78 deletion("export const radius = 0;"), 79 addition("export const radius = 8;"), 80 context('export const shadow = "none";') 81 ] 82 } 83 ] 84 } 85 ]; 86 87 type PlayContext = Pick<StoryContext<Record<string, unknown>>, "canvasElement">; 88 89 const hostsOf = (canvasElement: HTMLElement) => [ 90 ...canvasElement.querySelectorAll("diffs-container") 91 ]; 92 93 // pierre renders into shadow roots, testing library cannot see through 94 const shadowText = (canvasElement: HTMLElement) => 95 hostsOf(canvasElement) 96 .map((host) => host.shadowRoot?.textContent ?? "") 97 .join("\n"); 98 99 const rendersPatch = async (canvasElement: HTMLElement) => { 100 const hosts = hostsOf(canvasElement); 101 await expect(hosts.length).toBe(2); 102 await waitFor(() => expect(shadowText(canvasElement)).toContain("export const palette")); 103 await waitFor(() => expect(shadowText(canvasElement)).toContain("export const name")); 104 return hosts; 105 }; 106 107 const appviewChrome = async ({ canvasElement }: PlayContext) => { 108 const hosts = await rendersPatch(canvasElement); 109 for (const host of hosts) { 110 expect(host.shadowRoot?.querySelector("[data-diffs-header]")).toBeNull(); 111 expect(host.shadowRoot?.querySelector("[data-indicators='classic']")).not.toBeNull(); 112 } 113 // the hunks cover old lines 1-5 and 10-12, 4 collapse between 114 expect(hosts[0].shadowRoot?.querySelector("[data-separator='line-info']")).not.toBeNull(); 115 await waitFor(() => expect(shadowText(canvasElement)).toContain("4 unmodified lines")); 116 }; 117 118 const expectSplitColumns = (present: boolean) => { 119 return async ({ canvasElement }: PlayContext) => { 120 const hosts = await rendersPatch(canvasElement); 121 await waitFor(() => { 122 const found = hosts.some( 123 (host) => 124 host.shadowRoot?.querySelector("[data-deletions]") && 125 host.shadowRoot?.querySelector("[data-additions]") 126 ); 127 expect(found).toBe(present); 128 }); 129 }; 130 }; 131 132 // the separator bar must span the diff instead of clipping in the 44px 133 // gutter. the additions-side copy hides its text, only measure 134 // non-zero spans 135 const separatorNotClipped = async ({ canvasElement }: PlayContext) => { 136 const hosts = await rendersPatch(canvasElement); 137 await waitFor(() => { 138 const spans = hosts.flatMap((host) => [ 139 ...(host.shadowRoot?.querySelectorAll("[data-unmodified-lines]") ?? []) 140 ]); 141 const visible = spans.filter((span) => span.getBoundingClientRect().width > 0); 142 expect(visible.length).toBeGreaterThan(0); 143 for (const span of visible) { 144 const wrapper = span.closest("[data-separator-wrapper]"); 145 expect(wrapper).not.toBeNull(); 146 const wrapperRect = wrapper!.getBoundingClientRect(); 147 const spanRect = span.getBoundingClientRect(); 148 expect(wrapperRect.width).toBeGreaterThan(200); 149 expect(spanRect.x).toBeGreaterThanOrEqual(wrapperRect.x - 1); 150 expect(spanRect.right).toBeLessThanOrEqual(wrapperRect.right + 1); 151 } 152 }); 153 }; 154 155 const { Story } = defineMeta({ 156 title: "Repo/PierreDiff", 157 component: PierreDiff, 158 tags: ["autodocs"], 159 args: { files: files.map(toFileDiffMetadata) } 160 }); 161</script> 162 163<Story 164 name="Unified" 165 play={async (ctx) => { 166 await appviewChrome(ctx); 167 await expectSplitColumns(false)(ctx); 168 await separatorNotClipped(ctx); 169 }} 170/> 171 172<Story 173 name="Split" 174 args={{ diffStyle: "split" }} 175 play={async (ctx) => { 176 await expectSplitColumns(true)(ctx); 177 await separatorNotClipped(ctx); 178 }} 179/> 180 181<Story 182 name="Leading collapse" 183 args={{ files: offsetFiles.map(toFileDiffMetadata) }} 184 play={async ({ canvasElement }: PlayContext) => { 185 await waitFor(() => expect(shadowText(canvasElement)).toContain("19 unmodified lines")); 186 const host = hostsOf(canvasElement)[0]; 187 const separator = host.shadowRoot?.querySelector("[data-separator='line-info']"); 188 expect(separator).not.toBeNull(); 189 expect(separator?.hasAttribute("data-separator-first")).toBe(true); 190 }} 191/>