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

caleb531 / workday-time-calculator / 6990094913

25 Nov 2023 04:07PM UTC coverage: 93.467% (-0.2%) from 93.632%
6990094913

push

github

caleb531
Fix failing autocomplete test

Not sure why this works, but we can investigate later.

347 of 381 branches covered (0.0%)

Branch coverage included in aggregate %.

2128 of 2267 relevant lines covered (93.87%)

132.14 hits per line

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

74.25
/scripts/components/editor.js
1
import m from 'mithril';
3✔
2
import Quill from 'quill';
3✔
3
import 'quill/dist/quill.snow.css';
3✔
4
import appStorage from '../models/app-storage.js';
3✔
5
import EditorAutocompleter from '../models/editor-autocompleter.js';
3✔
6

3✔
7
class EditorComponent {
3✔
8
  oninit({ attrs: { preferences, selectedDate, onSetLogContents } }) {
3✔
9
    this.preferences = preferences;
174✔
10
    this.selectedDate = selectedDate.clone();
174✔
11
    this.onSetLogContents = onSetLogContents;
174✔
12
    this.autocompleter = new EditorAutocompleter({
174✔
13
      autocompleteMode: this.preferences.autocompleteMode
174✔
14
    });
174✔
15
    this.preferences.on('change:autocompleteMode', (key, newMode) => {
174✔
16
      this.autocompleter.setMode(newMode);
6✔
17
    });
174✔
18
  }
174✔
19

3✔
20
  onupdate({ attrs: { selectedDate } }) {
3✔
21
    if (!selectedDate.isSame(this.selectedDate)) {
325✔
22
      this.selectedDate = selectedDate.clone();
12✔
23
      this.getLogContentsForSelectedDate().then((logContents) => {
12✔
24
        this.setEditorText(logContents);
12✔
25
      });
12✔
26
    }
12✔
27
  }
325✔
28

3✔
29
  // Autocomplete the shown completion, if there is one; if not, run the
3✔
30
  // designated callback as a fallback
3✔
31
  autocomplete(range, options = {}) {
3✔
32
    const completionPlaceholder = this.autocompleter.completionPlaceholder;
×
33
    if (completionPlaceholder) {
×
34
      // Perform the two atomic operations silently and then trigger the event
×
35
      // listeners all at once; this solves a race condition where the
×
36
      // text-change event fires before the tab completion operations can finish
×
37
      this.editor.insertText(
×
38
        range.index,
×
39
        completionPlaceholder + ' ',
×
40
        'silent'
×
41
      );
×
42
      this.editor.setSelection(
×
43
        range.index + completionPlaceholder.length + 1,
×
44
        0,
×
45
        'silent'
×
46
      );
×
47
      this.editor.insertText(range.index, '', 'user');
×
48
      this.autocompleter.cancel();
×
49
    } else if (options.fallbackBehavior) {
×
50
      options.fallbackBehavior();
×
51
    }
×
52
  }
×
53

3✔
54
  async initializeEditor(editorContainer) {
3✔
55
    this.editor = new Quill(editorContainer, {
174✔
56
      theme: 'snow',
174✔
57
      placeholder:
174✔
58
        '1. Category One\n\t\ta. 9 to 12:15\n\t\t\t\ti. Did this\n2. Category Two\n\t\ta. 12:45 to 5\n\t\t\t\ti. Did that',
174✔
59
      formats: ['list', 'indent'],
174✔
60
      modules: {
174✔
61
        toolbar: [
174✔
62
          [{ list: 'bullet' }, { list: 'ordered' }],
174✔
63
          [{ indent: '-1' }, { indent: '+1' }]
174✔
64
        ],
174✔
65
        history: {
174✔
66
          // Do not add the editor contents to the Undo history when the app
174✔
67
          // initially loads, or when the selected date changes
174✔
68
          userOnly: true
174✔
69
        },
174✔
70
        keyboard: {
174✔
71
          bindings: {
174✔
72
            // Use <tab> and shift-<tab> to indent/un-indent; these must be
174✔
73
            // defined on editor initialization rather than via
174✔
74
            // keyboard.addBinding (see
174✔
75
            // <https://github.com/quilljs/quill/issues/1647>)
174✔
76
            tab: {
174✔
77
              key: 9,
174✔
78
              handler: (range) => {
174✔
79
                this.autocomplete(range, {
×
80
                  fallbackBehavior: () => {
×
81
                    this.editor.formatLine(range, { indent: '+1' }, 'user');
×
82
                    this.autocompleter.cancel();
×
83
                  }
×
84
                });
×
85
              }
×
86
            },
174✔
87
            arrowRight: {
174✔
88
              key: 39,
174✔
89
              handler: (range) => {
174✔
90
                this.autocomplete(range, {
×
91
                  fallbackBehavior: () => {
×
92
                    if (range.length) {
×
93
                      this.editor.setSelection(
×
94
                        range.index + range.length,
×
95
                        0,
×
96
                        'user'
×
97
                      );
×
98
                    } else {
×
99
                      this.editor.setSelection(
×
100
                        range.index + range.length + 1,
×
101
                        0,
×
102
                        'user'
×
103
                      );
×
104
                    }
×
105
                  }
×
106
                });
×
107
              }
×
108
            },
174✔
109
            shiftTab: {
174✔
110
              key: 9,
174✔
111
              shiftKey: true,
174✔
112
              handler: (range) => {
174✔
113
                this.editor.formatLine(range, { indent: '-1' }, 'user');
×
114
                this.autocompleter.cancel();
×
115
              }
×
116
            },
174✔
117
            indent: {
174✔
118
              // 221 corresponds to right bracket (']')
174✔
119
              key: 221,
174✔
120
              shortKey: true,
174✔
121
              handler: (range) => {
174✔
122
                this.editor.formatLine(range, { indent: '+1' }, 'user');
×
123
                this.autocompleter.cancel();
×
124
              }
×
125
            },
174✔
126
            unIndent: {
174✔
127
              // 219 corresponds to left bracket ('[')
174✔
128
              key: 219,
174✔
129
              shortKey: true,
174✔
130
              handler: (range) => {
174✔
131
                this.editor.formatLine(range, { indent: '-1' }, 'user');
×
132
                this.autocompleter.cancel();
×
133
              }
×
134
            },
174✔
135
            escape: {
174✔
136
              key: 27,
174✔
137
              handler: () => {
174✔
138
                if (this.autocompleter.completionPlaceholder) {
×
139
                  this.autocompleter.cancel();
×
140
                  m.redraw();
×
141
                }
×
142
              }
×
143
            }
174✔
144
          }
174✔
145
        }
174✔
146
      }
174✔
147
    });
174✔
148
    this.editor.on('selection-change', () => {
174✔
149
      this.autocompleter.cancel();
161✔
150
      m.redraw();
161✔
151
    });
174✔
152
    this.editor.on('text-change', (delta, oldDelta, source) => {
174✔
153
      if (source === 'user') {
459✔
154
        let logContents = this.editor.getContents();
273✔
155
        this.onSetLogContents(logContents);
273✔
156
        this.saveTextLog(logContents);
273✔
157
        this.autocompleter.fetchCompletions();
273✔
158
        m.redraw();
273✔
159
      }
273✔
160
      this.editor.focus();
459✔
161
    });
174✔
162
    this.autocompleter.on('receive', (placeholder) => {
174✔
163
      const selection = window.getSelection();
261✔
164
      // Do not calculate anything if the text cursor is not active inside the
261✔
165
      // editor, or if the parent element has not been set yet
261✔
166
      if (selection.type.toLowerCase() === 'none') {
261!
167
        console.log('return');
×
168
        return;
×
169
      }
×
170
      const range = selection.getRangeAt(0);
261✔
171
      const autocompleteParentElement =
261✔
172
        range.commonAncestorContainer.parentElement;
261✔
173
      autocompleteParentElement.setAttribute('data-autocomplete', placeholder);
261✔
174
      autocompleteParentElement.setAttribute(
261✔
175
        'data-testid',
261✔
176
        'log-editor-has-autocomplete-active'
261✔
177
      );
261✔
178
      m.redraw();
261✔
179
    });
174✔
180
    this.autocompleter.on('cancel', () => {
174✔
181
      document.querySelectorAll('[data-autocomplete]').forEach((element) => {
173✔
182
        element.removeAttribute('data-autocomplete');
×
183
        element.removeAttribute('data-testid');
×
184
      });
173✔
185
    });
174✔
186
    const logContents = await this.getLogContentsForSelectedDate();
174✔
187
    this.setEditorText(logContents);
174✔
188
    this.autocompleter.setEditor(this.editor);
174✔
189
  }
174✔
190

3✔
191
  async getLogContentsForSelectedDate() {
3✔
192
    let dateStorageId = this.getSelectedDateStorageId();
186✔
193
    let logContentsPromise = appStorage.get(dateStorageId);
186✔
194
    try {
186✔
195
      return (await logContentsPromise) || this.getDefaultLogContents();
186✔
196
    } catch (error) {
186!
197
      return this.getDefaultLogContents();
×
198
    }
×
199
  }
186✔
200

3✔
201
  getSelectedDateStorageId() {
3✔
202
    return `wtc-date-${this.selectedDate.format('l')}`;
459✔
203
  }
459✔
204

3✔
205
  getDefaultLogContents() {
3✔
206
    return {
96✔
207
      ops: [
96✔
208
        {
96✔
209
          insert: '\n'
96✔
210
        }
96✔
211
      ]
96✔
212
    };
96✔
213
  }
96✔
214

3✔
215
  setEditorText(logContents, source = 'api') {
3✔
216
    this.editor.setContents(logContents, source);
186✔
217
    this.onSetLogContents(logContents);
186✔
218
    m.redraw();
186✔
219
  }
186✔
220

3✔
221
  saveTextLog(logContents) {
3✔
222
    if (logContents.ops.length === 1 && logContents.ops[0].insert === '\n') {
273✔
223
      // If the contents of the current log are empty, delete the entry from
12✔
224
      // localStorage to conserve space
12✔
225
      appStorage.remove(this.getSelectedDateStorageId(this.selectedDate));
12✔
226
    } else {
273✔
227
      appStorage.set(
261✔
228
        this.getSelectedDateStorageId(this.selectedDate),
261✔
229
        logContents
261✔
230
      );
261✔
231
    }
261✔
232
  }
273✔
233

3✔
234
  view() {
3✔
235
    return m('div.log-editor-area', [
499✔
236
      m('div.log-editor[data-testid="log-editor"]', {
499✔
237
        oncreate: (vnode) => {
499✔
238
          this.initializeEditor(vnode.dom);
174✔
239
        }
174✔
240
      })
499✔
241
    ]);
499✔
242
  }
499✔
243
}
3✔
244

3✔
245
export default EditorComponent;
3✔
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