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

cofacts / rumors-site / 10029089778

21 Jul 2024 02:27PM UTC coverage: 71.353% (+0.1%) from 71.231%
10029089778

push

github

MrOrz
fix(ReplyRequestReason): show author of reply request

390 of 660 branches covered (59.09%)

Branch coverage included in aggregate %.

955 of 1225 relevant lines covered (77.96%)

11.78 hits per line

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

81.03
/components/Hyperlinks.js
1
import gql from 'graphql-tag';
2
import { t } from 'ttag';
3
import { Box, CircularProgress } from '@material-ui/core';
4
import { makeStyles } from '@material-ui/core/styles';
5
import { useQuery } from '@apollo/react-hooks';
6

7
import { HyperlinkIcon } from 'components/icons';
8

9
const useStyles = makeStyles(theme => ({
3✔
10
  root: {
11
    display: 'flex',
12
    flexFlow: 'row',
13
    flexWrap: 'wrap',
14
  },
15
  linkcard: {
16
    padding: '12px 16px',
17
    margin: '0 8px 8px 0',
18
    background: theme.palette.secondary[50],
19
    borderRadius: 8,
20
    maxWidth: '100%',
21
    '& h1': {
22
      overflow: 'hidden',
23
      textOverflow: 'ellipsis',
24
      whiteSpace: 'nowrap',
25
      margin: 0,
26
      marginBottom: 10,
27
      maxWidth: 'inherit',
28
      fontSize: 14,
29
    },
30
  },
31
  multilink: {
32
    [theme.breakpoints.down('sm')]: {
33
      flexWrap: 'nowrap',
34
      overflowX: 'auto',
35
      '--gutter': `${theme.spacing(2)}px`,
36
      marginLeft: 'calc(-1 * var(--gutter))',
37
      marginRight: 'calc(-1 * var(--gutter))',
38
      paddingLeft: 'var(--gutter)',
39
      paddingRight: 'var(--gutter)',
40
      '& article': {
41
        maxWidth: '90%',
42
      },
43
    },
44
  },
45
  url: {
46
    color: theme.palette.secondary[300],
47
    display: 'flex',
48
    alignItems: 'center',
49
    marginTop: 16,
50
    '& a': {
51
      color: theme.palette.secondary[300],
52
      display: 'block',
53
      whiteSpace: 'nowrap',
54
      overflow: 'hidden',
55
      textOverflow: 'ellipsis',
56
      textDecoration: 'none',
57
      marginLeft: 8,
58
    },
59
  },
60
  preview: {
61
    display: 'flex',
62
    borderLeft: `4px solid ${theme.palette.secondary[200]}`,
63
    paddingLeft: 10,
64
    paddingRight: 15,
65
    minHeight: 40,
66
    justifyContent: 'space-between',
67
    '& .summary': {
68
      color: theme.palette.secondary[500],
69
      overflow: 'hidden',
70
      margin: 0,
71
      display: '-webkit-box',
72
      boxOrient: 'vertical',
73
      textOverflow: 'ellipsis',
74
      lineClamp: 2,
75
    },
76
    '& .image': {
77
      marginLeft: 10,
78
      margin: 0,
79
      maxHeight: 60,
80
      minWidth: 60,
81
      background: `#ccc center center no-repeat`,
82
      backgroundSize: 'cover',
83
      backgroundImage: ({ topImageUrl }) => `url(${topImageUrl})`,
6✔
84
      borderRadius: 3,
85
    },
86
  },
87
  error: {
88
    color: 'firebrick',
89
    fontStyle: 'italic',
90
  },
91
}));
92

93
const HyperlinkData = gql`
1✔
94
  fragment HyperlinkData on Hyperlink {
95
    title
96
    url
97
    summary
98
    topImageUrl
99
    error
100
  }
101
`;
102

103
export const POLLING_QUERY = {
1✔
104
  articles: gql`
105
    query PollingArticleHyperlink($id: String!) {
106
      GetArticle(id: $id) {
107
        id
108
        hyperlinks {
109
          ...HyperlinkData
110
        }
111
      }
112
    }
113
    ${HyperlinkData}
114
  `,
115
  replies: gql`
116
    query PollingReplyHyperlink($id: String!) {
117
      GetReply(id: $id) {
118
        id
119
        hyperlinks {
120
          ...HyperlinkData
121
        }
122
      }
123
    }
124
    ${HyperlinkData}
125
  `,
126
};
127

128
/**
129
 * @param {string} error - One of ResolveError https://github.com/cofacts/url-resolver/blob/master/src/typeDefs/ResolveError.graphql
130
 */
131
function getErrorText(error) {
132
  switch (error) {
1!
133
    case 'NAME_NOT_RESOLVED':
134
      return t`Domain name cannot be resolved`;
×
135
    case 'UNSUPPORTED':
136
    case 'INVALID_URL':
137
      return t`URL is malformed or not supported`;
×
138
    case 'NOT_REACHABLE':
139
      return t`Cannot get data from URL`;
1✔
140
    case 'HTTPS_ERROR':
141
      return t`Target site contains HTTPS error`;
×
142
    default:
143
      return t`Unknown error`;
×
144
  }
145
}
146

147
/**
148
 * @param {object} props.hyperlink
149
 * @param {string} props.rel - rel prop for the hyperlink (other than noopener and noreferrer)
150
 */
151
function Hyperlink({ hyperlink, rel = '' }) {
3✔
152
  const { title, topImageUrl, error, url } = hyperlink;
3✔
153
  const summary = (hyperlink.summary || '').slice(0, 200);
3✔
154

155
  const classes = useStyles({ topImageUrl });
3✔
156

157
  return (
3✔
158
    <article className={classes.linkcard}>
159
      <h1 title={title}>{title}</h1>
160
      <div className={classes.preview}>
161
        <p className="summary" title={summary}>
162
          {summary}
163
        </p>
164
        {topImageUrl && <figure className="image" />}
4✔
165
        {error && <p className="error">{getErrorText(error)}</p>}
4✔
166
      </div>
167
      <span className={classes.url}>
168
        <HyperlinkIcon />
169
        <a href={url} target="_blank" rel={`noopener noreferrer ${rel}`}>
170
          {url}
171
        </a>
172
      </span>
173
    </article>
174
  );
175
}
176

177
function PollingHyperlink({ pollingType, pollingId }) {
178
  // The loaded data will populate apollo-client's normalized Article/Reply cache via apollo-client
179
  // automatic cache updates.
180
  // Ref: https://www.apollographql.com/docs/react/caching/cache-configuration/#automatic-cache-updates
181
  //
182
  // Therefore, we don't need to read query results here.
183
  //
184
  useQuery(POLLING_QUERY[pollingType], {
1✔
185
    variables: { id: pollingId },
186
    pollInterval: 2000,
187
  });
188

189
  return <CircularProgress />;
1✔
190
}
191

192
/**
193
 * @param {object[] | null} props.hyperlinks
194
 * @param {'articles'|'replies'?} props.pollingType - poll article or reply for hyperlinks when it's not loaded (null)
195
 * @param {string?} props.pollingId - polling article or reply id for hyperlinks when it's not loaded (null)
196
 * @param {string?} props.rel - rel prop for the hyperlink (other than noopener and noreferrer)
197
 */
198
function Hyperlinks({ hyperlinks, pollingType, pollingId, rel }) {
199
  const classes = useStyles();
3✔
200
  if (!((pollingId && pollingType) || (!pollingId && !pollingType))) {
3!
201
    throw new Error('pollingType and pollingId must be specified together');
×
202
  }
203

204
  if (hyperlinks && hyperlinks.length === 0) return null;
3✔
205

206
  let className = `${classes.root}`;
2✔
207
  if (hyperlinks && hyperlinks.length > 1) {
2✔
208
    className += ` ${classes.multilink}`;
1✔
209
  }
210
  return (
2✔
211
    <Box className={className} component="section" mt={2} mb={1}>
212
      {(hyperlinks || []).map((hyperlink, idx) => (
3✔
213
        <Hyperlink key={idx} hyperlink={hyperlink} rel={rel} />
3✔
214
      ))}
215
      {!hyperlinks && pollingId && (
4✔
216
        <PollingHyperlink pollingId={pollingId} pollingType={pollingType} />
217
      )}
218
    </Box>
219
  );
220
}
221

222
Hyperlinks.fragments = {
1✔
223
  HyperlinkData,
224
};
225

226
export default Hyperlinks;
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

© 2025 Coveralls, Inc