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

MarkUsProject / Markus / 13122420635

03 Feb 2025 08:39PM UTC coverage: 91.849% (-0.02%) from 91.865%
13122420635

Pull #7393

github

web-flow
Merge f9da04898 into 0597f5222
Pull Request #7393: update remote_autotest_settings_id validation

624 of 1361 branches covered (45.85%)

Branch coverage included in aggregate %.

23 of 23 new or added lines in 2 files covered. (100.0%)

85 existing lines in 11 files now uncovered.

41281 of 44263 relevant lines covered (93.26%)

120.26 hits per line

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

72.19
/app/javascript/Components/Result/text_viewer.jsx
1
import React from "react";
2
import Prism from "prismjs";
3

4
export class TextViewer extends React.PureComponent {
5
  constructor(props) {
6
    super(props);
12✔
7
    this.state = {
11✔
8
      copy_success: false,
9
      font_size: 1,
10
      content: null,
11
    };
12
    this.highlight_root = null;
11✔
13
    this.annotation_manager = null;
11✔
14
    this.raw_content = React.createRef();
11✔
15
    this.abortController = null;
11✔
16
  }
17

18
  getContentFromProps(props, state) {
19
    if (props.url) {
73✔
20
      return state.content;
69✔
21
    } else {
22
      return props.content;
4✔
23
    }
24
  }
25

26
  // Retrieves content used by the component.
27
  getContent() {
28
    return this.getContentFromProps(this.props, this.state);
39✔
29
  }
30

31
  componentWillUnmount() {
32
    if (this.abortController) {
11✔
33
      this.abortController.abort();
9✔
34
    }
35
  }
36

37
  componentDidMount() {
38
    this.highlight_root = this.raw_content.current.parentNode;
11✔
39

40
    // Fetch content from a URL if it is passed as a prop. The URL should point to plaintext data.
41
    if (this.props.url) {
11✔
42
      this.props.setLoadingCallback(true);
9✔
43
      this.fetchContent(this.props.url)
9✔
44
        .then(content =>
45
          this.setState({content: content}, () => this.props.setLoadingCallback(false))
6✔
46
        )
47
        .catch(error => {
48
          this.props.setLoadingCallback(false);
3✔
49
          if (error instanceof DOMException) return;
3!
50
          console.error(error);
3✔
51
        });
52
    }
53

54
    if (this.getContent()) {
11✔
55
      this.ready_annotations();
2✔
56
    }
57
  }
58

59
  fetchContent(url) {
60
    if (this.abortController) {
12✔
61
      // Stops ongoing fetch requests. It's ok to call .abort() after the fetch has already completed,
62
      // fetch simply ignores it.
63
      this.abortController.abort();
3✔
64
    }
65
    // Reinitialize the controller, because the signal can't be reused after the request has been aborted.
66
    this.abortController = new AbortController();
12✔
67

68
    return fetch(url, {signal: this.abortController.signal})
12✔
69
      .then(response => {
70
        if (response.status === 413) {
11✔
71
          const errorMessage = I18n.t("submissions.oversize_submission_file");
2✔
72
          this.props.setErrorMessageCallback(errorMessage);
2✔
73
          throw new Error(errorMessage);
2✔
74
        } else {
75
          return response.text();
9✔
76
        }
77
      })
78
      .then(content => content.replace(/\r?\n/gm, "\n"));
9✔
79
  }
80

81
  componentDidUpdate(prevProps, prevState) {
82
    if (this.props.url && this.props.url !== prevProps.url) {
17✔
83
      // The URL has updated, so the content needs to be fetched using the new URL.
84
      this.props.setLoadingCallback(true);
3✔
85
      this.fetchContent(this.props.url)
3✔
86
        .then(content =>
87
          this.setState({content: content}, () => {
3✔
88
            this.props.setLoadingCallback(false);
3✔
89
            this.postInitContent(prevProps, prevState);
3✔
90
          })
91
        )
92
        .catch(error => {
UNCOV
93
          this.props.setLoadingCallback(false);
×
94
          if (error instanceof DOMException) return;
×
95
          console.error(error);
×
96
        });
97
    } else {
98
      this.postInitContent(prevProps, prevState);
14✔
99
    }
100
  }
101

102
  postInitContent(prevProps, prevState) {
103
    const content = this.getContentFromProps(this.props, this.state);
17✔
104
    const prevContent = this.getContentFromProps(prevProps, prevState);
17✔
105

106
    if (content && (content !== prevContent || this.props.annotations !== prevProps.annotations)) {
17✔
107
      this.ready_annotations();
11✔
108
      this.setState({copy_success: false});
11✔
109
    } else if (this.props.focusLine !== prevProps.focusLine) {
6!
UNCOV
110
      this.scrollToLine(this.props.focusLine);
×
111
    }
112
    if (prevState.font_size !== this.state.font_size && this.highlight_root !== null) {
17!
UNCOV
113
      this.highlight_root.style.fontSize = this.state.font_size + "em";
×
114
    }
115
  }
116

117
  /**
118
   * Post-processes text contents and display in three ways:
119
   *
120
   * 1. Apply syntax highlighting
121
   * 2. Display annotations
122
   * 3. Scroll to line numbered this.props.focusLine
123
   */
124
  ready_annotations = () => {
11✔
125
    this.run_syntax_highlighting();
13✔
126

127
    if (this.annotation_manager !== null) {
13!
UNCOV
128
      this.annotation_manager.annotation_text_displayer.hide();
×
129
    }
130

131
    this.highlight_root.style.font_size = this.state.fontSize + "em";
13✔
132

133
    if (this.props.resultView) {
13!
UNCOV
134
      window.annotation_type = ANNOTATION_TYPES.CODE;
×
135

UNCOV
136
      window.annotation_manager = new TextAnnotationManager(this.raw_content.current.children);
×
137
      this.annotation_manager = window.annotation_manager;
×
138
    }
139

140
    this.props.annotations.forEach(this.display_annotation);
13✔
141
    this.scrollToLine(this.props.focusLine);
13✔
142
  };
143

144
  run_syntax_highlighting = () => {
11✔
145
    Prism.highlightElement(this.raw_content.current, false);
13✔
146
    let nodeLines = [];
13✔
147
    let currLine = document.createElement("span");
13✔
148
    currLine.classList.add("source-line");
13✔
149
    let currChildren = [];
13✔
150
    for (let node of this.raw_content.current.childNodes) {
13✔
151
      // Note: SourceCodeLine.glow assumes text nodes are wrapped in <span> elements
152
      let textContainer = document.createElement("span");
38✔
153
      let className = node.nodeType === Node.TEXT_NODE ? "" : node.className;
38✔
154
      textContainer.className = className;
38✔
155

156
      const splits = node.textContent.split("\n");
38✔
157
      for (let i = 0; i < splits.length - 1; i++) {
38✔
158
        textContainer.textContent = splits[i] + "\n";
2✔
159
        currLine.append(...currChildren, textContainer);
2✔
160
        nodeLines.push(currLine);
2✔
161
        currLine = document.createElement("span");
2✔
162
        currLine.classList.add("source-line");
2✔
163
        currChildren = [];
2✔
164
        textContainer = document.createElement("span");
2✔
165
        textContainer.className = className;
2✔
166
      }
167

168
      textContainer.textContent = splits[splits.length - 1];
38✔
169
      currLine.append(...currChildren, textContainer);
38✔
170
    }
171
    if (currLine.textContent.length > 0) {
13✔
172
      nodeLines.push(currLine);
12✔
173
    }
174
    this.raw_content.current.replaceChildren(
13✔
175
      ...nodeLines,
176
      this.raw_content.current.lastChild.cloneNode(true)
177
    );
178
  };
179

180
  change_font_size = delta => {
11✔
UNCOV
181
    this.setState({font_size: Math.max(this.state.font_size + delta, 0.25)});
×
182
  };
183

184
  display_annotation = annotation => {
11✔
185
    let content;
UNCOV
186
    if (!annotation.deduction) {
×
187
      content = annotation.content;
×
188
    } else {
UNCOV
189
      content =
×
190
        annotation.content + " [" + annotation.criterion_name + ": -" + annotation.deduction + "]";
191
    }
192

UNCOV
193
    if (annotation.is_remark) {
×
194
      content += ` (${I18n.t("results.annotation.remark_flag")})`;
×
195
    }
196

UNCOV
197
    this.annotation_manager.addAnnotation(
×
198
      annotation.annotation_text_id,
199
      content,
200
      {
201
        start: parseInt(annotation.line_start, 10),
202
        end: parseInt(annotation.line_end, 10),
203
        column_start: parseInt(annotation.column_start, 10),
204
        column_end: parseInt(annotation.column_end, 10),
205
      },
206
      annotation.id,
207
      annotation.is_remark
208
    );
209
  };
210

211
  // Scroll to display the given line.
212
  scrollToLine = lineNumber => {
11✔
213
    if (this.highlight_root === null || lineNumber === undefined || lineNumber === null) {
13!
214
      return;
13✔
215
    }
216

UNCOV
217
    const line = this.highlight_root.querySelector(`span.source-line:nth-of-type(${lineNumber})`);
×
218
    if (line) {
×
219
      line.scrollIntoView();
×
220
    }
221
  };
222

223
  copyToClipboard = () => {
11✔
UNCOV
224
    const content = this.getContent();
×
225

226
    // Prevents from copying `null` or `undefined` to the clipboard. An empty string is ok to copy.
UNCOV
227
    if (content || content === "") {
×
228
      navigator.clipboard.writeText(content).then(() => {
×
229
        this.setState({copy_success: true});
×
230
      });
231
    } else {
UNCOV
232
      console.warn(`Tried to copy content with value ${content} to the clipboard.`);
×
233
      this.setState({copy_success: false});
×
234
    }
235
  };
236

237
  render() {
238
    const preElementName = `code-${this.props.submission_file_id}`;
28✔
239

240
    return (
28✔
241
      <React.Fragment>
242
        <div className="toolbar">
243
          <div className="toolbar-actions">
244
            <a href="#" onClick={this.copyToClipboard}>
245
              {this.state.copy_success ? "✔ " : ""}
28!
246
              {I18n.t("results.copy_text")}
247
            </a>
UNCOV
248
            <a href="#" onClick={() => this.change_font_size(0.25)}>
×
249
              +A
250
            </a>
UNCOV
251
            <a href="#" onClick={() => this.change_font_size(-0.25)}>
×
252
              -A
253
            </a>
254
          </div>
255
        </div>
256
        <pre name={preElementName} className={`line-numbers`}>
257
          <code ref={this.raw_content} className={`language-${this.props.type}`}>
258
            {this.getContent()}
259
          </code>
260
        </pre>
261
      </React.Fragment>
262
    );
263
  }
264
}
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