• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

iamogbz / letters-from-the-abyss / #75

02 Feb 2025 08:40PM UTC coverage: 61.792% (+0.2%) from 61.579%
#75

push

iamogbz
wip: include a way to view unpublished items

16 of 35 branches covered (45.71%)

Branch coverage included in aggregate %.

10 of 11 new or added lines in 1 file covered. (90.91%)

1 existing line in 1 file now uncovered.

115 of 177 relevant lines covered (64.97%)

6.36 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.44
/src/components/PoemCollection.tsx
1
import React from "react";
2
// import ReactList from "react-list";
3
import { pick } from "lodash";
4
import { QueryParams } from "../utils/constants";
5
import { isPageLocallyLiked, logPoemLike } from "../utils/hitCounter";
6
import { sharePoem } from "../utils/share";
7
import stories from "../utils/stories.json";
8
import { PoemDetails, PoemImage } from "./PoemCard";
9
import "./PoemCollection.css";
10
import { useOnNavigation } from "./hooks/useOnNavigation";
11
import { useValue } from "./hooks/useValue";
12

13
function Button(props: React.InputHTMLAttributes<HTMLInputElement>) {
14
  return <input {...props} type="button" />;
8✔
15
}
16

17
function PoemCollection() {
18
  const pageHash = useValue(() => document.location.hash);
3✔
19

20
  const searchParams = new URLSearchParams(window.location.search);
2✔
21
  const includeAll = searchParams.has(QueryParams.ALL);
2✔
22
  const includeDrafts = includeAll || searchParams.has(QueryParams.UNPUBLISHED);
2✔
23
  const includePublished = includeAll || !includeDrafts;
2✔
24
  const poemEntries = React.useMemo(() => {
2✔
25
    const storyEntries = pick(stories.data.published, "welcome", "credits");
2✔
26
    if (includePublished) {
2!
27
      Object.assign(storyEntries, stories.data.published);
2✔
28
    }
29
    if (includeDrafts) {
2!
NEW
30
      Object.assign(storyEntries, stories.data.drafts);
×
31
    }
32
    const [first, ...rest] = Object.entries(storyEntries).sort((a, b) => {
2✔
33
      // sort poems by date
34
      return a[1].localeCompare(b[1]);
272✔
35
    });
36
    const [last, ...poems] = rest.reverse();
2✔
37
    // randomise the poems but keep the first and last in place
38
    return [first, ...poems.sort(() => Math.random() - 0.5), last];
241✔
39
  }, [includeDrafts, includePublished]);
40

41
  const [isCurrentPoemLiked, setIsCurrentPoemLiked] = React.useState(false);
2✔
42
  const likePoem = React.useCallback(async () => {
2✔
43
    await logPoemLike();
×
44
    setIsCurrentPoemLiked(isPageLocallyLiked());
×
45
    alert("Awesome, thank you for your feedback!");
×
46
  }, []);
47
  useOnNavigation({
2✔
48
    callback: () => setIsCurrentPoemLiked(isPageLocallyLiked()),
×
49
  });
50

51
  const pages = React.useMemo(() => {
2✔
52
    return Array(poemEntries.length)
2✔
53
      .fill(null)
54
      .map((_, i) => {
55
        const [title, date] = poemEntries[i];
70✔
56
        const entryProps = { date, key: `${title}-${i}`, title };
70✔
57
        return <PoemDetails open={true} {...entryProps} />;
70✔
58
      });
59
  }, [poemEntries]);
60

61
  const openPageNumber = React.useMemo(
2✔
62
    () =>
63
      Math.max(
2✔
64
        0,
65
        pages.findIndex((el) => pageHash.endsWith(el.props.title))
70✔
66
      ),
67
    [pageHash, pages]
68
  );
69

70
  const isOnFirstPage = openPageNumber === 0;
2✔
71
  const isOnLastPage = openPageNumber === pages.length - 1;
2✔
72

73
  const goToPage = React.useCallback(
2✔
74
    (pageNum: number) => {
75
      document.location.hash = poemEntries[pageNum][0];
×
76
    },
77
    [poemEntries]
78
  );
79

80
  const goToNextPage = React.useCallback(() => {
2✔
81
    goToPage(Math.min(openPageNumber + 1, poemEntries.length - 1));
×
82
  }, [goToPage, openPageNumber, poemEntries.length]);
83
  const goToPrevPage = React.useCallback(() => {
2✔
84
    goToPage(Math.max(openPageNumber - 1, 0));
×
85
  }, [goToPage, openPageNumber]);
86

87
  const [title, date] = poemEntries[openPageNumber];
2✔
88
  const entryProps = { date, title };
2✔
89

90
  React.useEffect(() => {
2✔
91
    const poemTitleTopPx = window.screen.height / 2;
×
92
    window.scrollTo({ top: poemTitleTopPx, behavior: "smooth" });
×
93
  }, [title]);
94

95
  return (
2✔
96
    <>
97
      <Button
98
        id="prev-btn"
99
        disabled={isOnFirstPage}
100
        value="Back ⬅️"
101
        onClick={goToPrevPage}
102
      />
103
      <Button
104
        id="next-btn"
105
        disabled={isOnLastPage}
106
        value="➡️ Next"
107
        onClick={goToNextPage}
108
      />
109
      <Button
110
        id="like-btn"
111
        value={isCurrentPoemLiked ? "Liked ❤️" : "Like ❤️"}
2!
112
        title="Like"
113
        onClick={likePoem}
114
        disabled={isCurrentPoemLiked}
115
      ></Button>
116
      <Button
117
        id="share-btn"
118
        value="🔗 Link"
119
        title="Share"
120
        onClick={sharePoem}
121
      ></Button>
122
      <PoemImage {...entryProps} />
123
      <PoemDetails {...entryProps} />
124
    </>
125
  );
126
}
127

128
PoemCollection.wrapperStyles = {
2✔
129
  // display: "flex",
130
  // flexDirection: "column",
131
  // alignItems: "center",
132
  // justifyContent: "center",
133
  get padding() {
134
    return this.gap;
×
135
  },
136
  // position: "relative",
137
  zIndex: 1,
138
} as React.CSSProperties;
139

140
export default PoemCollection;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc