Skip to content

Commit 4ebd5f9

Browse files
authored
fix: handle consecutive added empty file diff (#38)
* fix: handle consecutive-empty-files * fix: handle no-prefix
1 parent 4bf6dff commit 4ebd5f9

File tree

9 files changed

+1512
-8
lines changed

9 files changed

+1512
-8
lines changed

src/__fixtures__/31-no-prefix

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/epicshop/diff/advanced-react-apis/09.03.solution/dn2ncwjsbmo/index.test.ts var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/epicshop/diff/advanced-react-apis/09.03.solution/dn2ncwjsbmo/index.test.ts
2+
new file mode 100644
3+
index 0000000..e69de29
4+
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/epicshop/diff/advanced-react-apis/playground/dn2ncwjsbmo/index.tsx var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/epicshop/diff/advanced-react-apis/09.03.solution/dn2ncwjsbmo/index.tsx
5+
index 4d68325..fd576f7 100644
6+
--- var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/epicshop/diff/advanced-react-apis/playground/dn2ncwjsbmo/index.tsx
7+
+++ var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/epicshop/diff/advanced-react-apis/09.03.solution/dn2ncwjsbmo/index.tsx
8+
@@ -1,190 +1,54 @@
9+
-import { createContext, useEffect, useState, use, useCallback } from 'react'
10+
+import { Suspense, useSyncExternalStore } from 'react'
11+
import * as ReactDOM from 'react-dom/client'
12+
-import {
13+
- type BlogPost,
14+
- generateGradient,
15+
- getMatchingPosts,
16+
-} from '#shared/blog-posts'
17+
-import { setGlobalSearchParams } from '#shared/utils'
18+
19+
-type SearchParamsTuple = readonly [
20+
- URLSearchParams,
21+
- typeof setGlobalSearchParams,
22+
-]
23+
-const SearchParamsContext = createContext<SearchParamsTuple>([
24+
- new URLSearchParams(window.location.search),
25+
- setGlobalSearchParams,
26+
-])
27+
-
28+
-function SearchParamsProvider({ children }: { children: React.ReactNode }) {
29+
- const [searchParams, setSearchParamsState] = useState(
30+
- () => new URLSearchParams(window.location.search),
31+
- )
32+
+export function makeMediaQueryStore(mediaQuery: string) {
33+
+ function getSnapshot() {
34+
+ return window.matchMedia(mediaQuery).matches
35+
+ }
36+
37+
- useEffect(() => {
38+
- function updateSearchParams() {
39+
- setSearchParamsState((prevParams) => {
40+
- const newParams = new URLSearchParams(window.location.search)
41+
- return prevParams.toString() === newParams.toString()
42+
- ? prevParams
43+
- : newParams
44+
- })
45+
+ function subscribe(callback: () => void) {
46+
+ const mediaQueryList = window.matchMedia(mediaQuery)
47+
+ mediaQueryList.addEventListener('change', callback)
48+
+ return () => {
49+
+ mediaQueryList.removeEventListener('change', callback)
50+
}
51+
- window.addEventListener('popstate', updateSearchParams)
52+
- return () => window.removeEventListener('popstate', updateSearchParams)
53+
- }, [])
54+
-
55+
- const setSearchParams = useCallback(
56+
- (...args: Parameters<typeof setGlobalSearchParams>) => {
57+
- const searchParams = setGlobalSearchParams(...args)
58+
- setSearchParamsState((prevParams) => {
59+
- return prevParams.toString() === searchParams.toString()
60+
- ? prevParams
61+
- : searchParams
62+
- })
63+
- return searchParams
64+
- },
65+
- [],
66+
- )
67+
-
68+
- const searchParamsTuple = [searchParams, setSearchParams] as const
69+
-
70+
- return (
71+
- <SearchParamsContext value={searchParamsTuple}>
72+
- {children}
73+
- </SearchParamsContext>
74+
- )
75+
-}
76+
-
77+
-function useSearchParams() {
78+
- return use(SearchParamsContext)
79+
-}
80+
-
81+
-const getQueryParam = (params: URLSearchParams) => params.get('query') ?? ''
82+
-
83+
-function App() {
84+
- return (
85+
- <SearchParamsProvider>
86+
- <div className="app">
87+
- <Form />
88+
- <MatchingPosts />
89+
- </div>
90+
- </SearchParamsProvider>
91+
- )
92+
-}
93+
-
94+
-function Form() {
95+
- const [searchParams, setSearchParams] = useSearchParams()
96+
- const query = getQueryParam(searchParams)
97+
-
98+
- const words = query.split(' ').map((w) => w.trim())
99+
-
100+
- const dogChecked = words.includes('dog')
101+
- const catChecked = words.includes('cat')
102+
- const caterpillarChecked = words.includes('caterpillar')
103+
-
104+
- function handleCheck(tag: string, checked: boolean) {
105+
- const newWords = checked ? [...words, tag] : words.filter((w) => w !== tag)
106+
- setSearchParams(
107+
- { query: newWords.filter(Boolean).join(' ').trim() },
108+
- { replace: true },
109+
- )
110+
}
111+
112+
- return (
113+
- <form onSubmit={(e) => e.preventDefault()}>
114+
- <div>
115+
- <label htmlFor="searchInput">Search:</label>
116+
- <input
117+
- id="searchInput"
118+
- name="query"
119+
- type="search"
120+
- value={query}
121+
- onChange={(e) =>
122+
- setSearchParams({ query: e.currentTarget.value }, { replace: true })
123+
- }
124+
- />
125+
- </div>
126+
- <div>
127+
- <label>
128+
- <input
129+
- type="checkbox"
130+
- checked={dogChecked}
131+
- onChange={(e) => handleCheck('dog', e.currentTarget.checked)}
132+
- />{' '}
133+
- 🐶 dog
134+
- </label>
135+
- <label>
136+
- <input
137+
- type="checkbox"
138+
- checked={catChecked}
139+
- onChange={(e) => handleCheck('cat', e.currentTarget.checked)}
140+
- />{' '}
141+
- 🐱 cat
142+
- </label>
143+
- <label>
144+
- <input
145+
- type="checkbox"
146+
- checked={caterpillarChecked}
147+
- onChange={(e) =>
148+
- handleCheck('caterpillar', e.currentTarget.checked)
149+
- }
150+
- />{' '}
151+
- 🐛 caterpillar
152+
- </label>
153+
- </div>
154+
- </form>
155+
- )
156+
+ return function useMediaQuery() {
157+
+ return useSyncExternalStore(subscribe, getSnapshot)
158+
+ }
159+
}
160+
161+
-function MatchingPosts() {
162+
- const [searchParams] = useSearchParams()
163+
- const query = getQueryParam(searchParams)
164+
- const matchingPosts = getMatchingPosts(query)
165+
+const useNarrowMediaQuery = makeMediaQueryStore('(max-width: 600px)')
166+
167+
- return (
168+
- <ul className="post-list">
169+
- {matchingPosts.map((post) => (
170+
- <Card key={post.id} post={post} />
171+
- ))}
172+
- </ul>
173+
- )
174+
+function NarrowScreenNotifier() {
175+
+ const isNarrow = useNarrowMediaQuery()
176+
+ return isNarrow ? 'You are on a narrow screen' : 'You are on a wide screen'
177+
}
178+
179+
-function Card({ post }: { post: BlogPost }) {
180+
- const [isFavorited, setIsFavorited] = useState(false)
181+
+function App() {
182+
return (
183+
- <li>
184+
- {isFavorited ? (
185+
- <button
186+
- aria-label="Remove favorite"
187+
- onClick={() => setIsFavorited(false)}
188+
- >
189+
- ❤️
190+
- </button>
191+
- ) : (
192+
- <button aria-label="Add favorite" onClick={() => setIsFavorited(true)}>
193+
- 🤍
194+
- </button>
195+
- )}
196+
- <div
197+
- className="post-image"
198+
- style={{ background: generateGradient(post.id) }}
199+
- />
200+
- <a
201+
- href={post.id}
202+
- onClick={(event) => {
203+
- event.preventDefault()
204+
- alert(`Great! Let's go to ${post.id}!`)
205+
- }}
206+
- >
207+
- <h2>{post.title}</h2>
208+
- <p>{post.description}</p>
209+
- </a>
210+
- </li>
211+
+ <div>
212+
+ <div>This is your narrow screen state:</div>
213+
+ <Suspense fallback="">
214+
+ <NarrowScreenNotifier />
215+
+ </Suspense>
216+
+ </div>
217+
)
218+
}
219+
220+
const rootEl = document.createElement('div')
221+
document.body.append(rootEl)
222+
-ReactDOM.createRoot(rootEl).render(<App />)
223+
+// 🦉 here's how we pretend we're server-rendering
224+
+rootEl.innerHTML = (await import('react-dom/server')).renderToString(<App />)
225+
+
226+
+// 🦉 here's how we simulate a delay in hydrating with client-side js
227+
+await new Promise((resolve) => setTimeout(resolve, 1000))
228+
+
229+
+ReactDOM.hydrateRoot(rootEl, <App />, {
230+
+ onRecoverableError(error) {
231+
+ if (String(error).includes('Missing getServerSnapshot')) return
232+
+
233+
+ console.error(error)
234+
+ },
235+
+})
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
diff --git a/content b/content
2+
new file mode 100644
3+
index 0000000..6b584e8
4+
--- /dev/null
5+
+++ b/content
6+
@@ -0,0 +1 @@
7+
+content
8+
\ No newline at end of file
9+
diff --git a/empty b/empty
10+
new file mode 100644
11+
index 0000000..e69de29
12+
diff --git a/empty2 b/empty2
13+
new file mode 100644
14+
index 0000000..e69de29

src/__tests__/31-no-prefix.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { getFixture } from './test-utils';
2+
import parseGitDiff from '../parse-git-diff';
3+
4+
describe('issue 31-no-prefix', () => {
5+
const fixture = getFixture('31-no-prefix');
6+
7+
it('parse `31-no-prefix`', () => {
8+
expect(parseGitDiff(fixture, { noPrefix: true })).toMatchSnapshot();
9+
});
10+
});

src/__tests__/31.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { getFixture } from './test-utils';
22
import parseGitDiff from '../parse-git-diff';
33

4-
describe.only('issue 31', () => {
4+
describe('issue 31', () => {
55
const fixture = getFixture('31');
66

77
it('parse `31`', () => {

0 commit comments

Comments
 (0)