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

CBIIT / bento-icdc-frontend / 26530955228

27 May 2026 06:34PM UTC coverage: 17.321% (-8.4%) from 25.73%
26530955228

Pull #1607

github

web-flow
Merge 361b68ce9 into bc935f39c
Pull Request #1607: Feature/ai test studio - ICDC-4165 & ICDC-4171

306 of 2599 branches covered (11.77%)

Branch coverage included in aggregate %.

1 of 4346 new or added lines in 75 files covered. (0.02%)

2 existing lines in 2 files now uncovered.

2197 of 11852 relevant lines covered (18.54%)

0.41 hits per line

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

0.0
/src/components/DataDictionaryComponent/DataDictionary/utils.generated.test.js
1
import FileSaver from 'file-saver';
2
import JSZip from 'jszip';
3
import {
4
  truncateLines,
5
  getType,
6
  downloadTemplate,
7
  downloadMultiTemplate,
8
  parseDictionaryNodes,
9
  getPropertyDescription,
10
  getSearchHistoryItems,
11
  addSearchHistoryItems,
12
  clearSearchHistoryItems,
13
} from './utils';
14

NEW
15
const mockFile = jest.fn();
×
NEW
16
const mockGenerateAsync = jest.fn().mockResolvedValue('blob-content');
×
17

NEW
18
jest.mock('file-saver', () => ({
×
19
  __esModule: true,
20
  default: {
21
    saveAs: jest.fn(),
22
  },
23
}));
24

NEW
25
jest.mock('jszip', () => {
×
NEW
26
  const MockJSZip = jest.fn().mockImplementation(() => ({
×
27
    file: mockFile,
28
    generateAsync: mockGenerateAsync,
29
  }));
30

NEW
31
  return {
×
32
    __esModule: true,
33
    default: MockJSZip,
34
  };
35
});
36

NEW
37
const flushPromises = async () => {
×
NEW
38
  await Promise.resolve();
×
NEW
39
  await Promise.resolve();
×
NEW
40
  await Promise.resolve();
×
41
};
42

NEW
43
describe('utils', () => {
×
NEW
44
  beforeEach(() => {
×
NEW
45
    jest.clearAllMocks();
×
NEW
46
    localStorage.clear();
×
47
  });
48

NEW
49
  describe('truncateLines', () => {
×
NEW
50
    it('returns a single line when input fits within maxCharInRow', () => {
×
NEW
51
      expect(truncateLines('a bb ccc', 10, 12)).toEqual(['a bb ccc']);
×
52
    });
53

NEW
54
    it('splits into multiple lines when words exceed maxCharInRow', () => {
×
NEW
55
      expect(truncateLines('hello world', 5, 12)).toEqual(['hello', 'world']);
×
56
    });
57

NEW
58
    it('hyphen-breaks a long word exceeding breakwordMinLength', () => {
×
NEW
59
      expect(truncateLines('abcdefghijklmno', 5, 12)).toEqual([
×
60
        'abcd-',
61
        'efgh-',
62
        'ijkl-',
63
        'mno',
64
      ]);
65
    });
66

NEW
67
    it('handles empty string input', () => {
×
NEW
68
      expect(truncateLines('', 10, 12)).toEqual(['']);
×
69
    });
70
  });
71

NEW
72
  describe('getType', () => {
×
NEW
73
    it('returns scalar type string when property.type is string', () => {
×
NEW
74
      expect(getType({ type: 'string' })).toBe('string');
×
75
    });
76

NEW
77
    it('returns enum array when property.enum is present', () => {
×
NEW
78
      expect(getType({ enum: ['A', 'B'] })).toEqual(['A', 'B']);
×
79
    });
80

NEW
81
    it('flattens oneOf nested types and enums', () => {
×
NEW
82
      const property = {
×
83
        oneOf: [{ enum: ['X', 'Y'] }, { type: 'integer' }],
84
      };
NEW
85
      expect(getType(property)).toEqual(['X', 'Y', 'integer']);
×
86
    });
87

NEW
88
    it('flattens anyOf nested types and enums', () => {
×
NEW
89
      const property = {
×
90
        anyOf: [
91
          { enum: ['P'] },
92
          { type: 'number' },
93
          { oneOf: [{ type: 'null' }] },
94
        ],
95
      };
NEW
96
      expect(getType(property)).toEqual(['P', 'number', 'null']);
×
97
    });
98

NEW
99
    it('returns UNDEFINED when no type information is available', () => {
×
NEW
100
      expect(getType({})).toBe('UNDEFINED');
×
101
    });
102
  });
103

NEW
104
  describe('downloadTemplate', () => {
×
105
    let openSpy;
106

NEW
107
    beforeEach(() => {
×
NEW
108
      openSpy = jest.spyOn(window, 'open').mockImplementation(() => {});
×
109
    });
110

NEW
111
    afterEach(() => {
×
NEW
112
      openSpy.mockRestore();
×
113
    });
114

NEW
115
    it('opens a new window with correct URL for valid format tsv', () => {
×
NEW
116
      downloadTemplate('tsv', 'case');
×
NEW
117
      expect(openSpy).toHaveBeenCalledWith('FIXMEcase?format=tsv');
×
118
    });
119

NEW
120
    it('opens a new window with correct URL for valid format json', () => {
×
NEW
121
      downloadTemplate('json', 'file');
×
NEW
122
      expect(openSpy).toHaveBeenCalledWith('FIXMEfile?format=json');
×
123
    });
124

NEW
125
    it('does nothing for invalid format', () => {
×
NEW
126
      downloadTemplate('csv', 'node');
×
NEW
127
      expect(openSpy).not.toHaveBeenCalled();
×
128
    });
129
  });
130

NEW
131
  describe('downloadMultiTemplate', () => {
×
NEW
132
    beforeEach(() => {
×
NEW
133
      mockFile.mockClear();
×
NEW
134
      mockGenerateAsync.mockClear();
×
NEW
135
      jest
×
136
        .spyOn(Date.prototype, 'toLocaleDateString')
137
        .mockReturnValue('2024-01-02');
NEW
138
      jest
×
139
        .spyOn(Date.prototype, 'toLocaleTimeString')
140
        .mockReturnValue('03:04:05 PM');
NEW
141
      global.fetch = jest.fn();
×
142
    });
143

NEW
144
    afterEach(() => {
×
NEW
145
      delete global.fetch;
×
NEW
146
      jest.restoreAllMocks();
×
147
    });
148

NEW
149
    it('returns early and does not perform any action for invalid format', async () => {
×
NEW
150
      downloadMultiTemplate('csv', { A: 'A.tsv' }, [], 'Node', 'v1');
×
151

NEW
152
      await flushPromises();
×
153

NEW
154
      expect(global.fetch).not.toHaveBeenCalled();
×
NEW
155
      expect(FileSaver.saveAs).not.toHaveBeenCalled();
×
NEW
156
      expect(JSZip).not.toHaveBeenCalled();
×
157
    });
158

NEW
159
    it('fetches all node templates, creates README, zips and saves for tsv', async () => {
×
NEW
160
      const format = 'tsv';
×
NEW
161
      const nodesToDownload = {
×
162
        case: 'case.tsv',
163
        sample: 'sample.tsv',
164
      };
NEW
165
      const allRoutes = [
×
166
        ['Project', 'Case', 'Sample'],
167
        ['Project', 'Study'],
168
      ];
NEW
169
      const clickingNodeName = 'Sample';
×
NEW
170
      const dictionaryVersion = '2024.01';
×
NEW
171
      const base = 'FIXME';
×
172

NEW
173
      global.fetch.mockImplementation(url => {
×
NEW
174
        const responseMap = {
×
175
          [`${base}case?format=${format}`]: 'case-content',
176
          [`${base}sample?format=${format}`]: 'sample-content',
177
        };
178

NEW
179
        return Promise.resolve({
×
180
          ok: true,
NEW
181
          text: () => Promise.resolve(responseMap[url] ?? 'content'),
×
182
        });
183
      });
184

NEW
185
      downloadMultiTemplate(
×
186
        format,
187
        nodesToDownload,
188
        allRoutes,
189
        clickingNodeName,
190
        dictionaryVersion
191
      );
192

NEW
193
      await flushPromises();
×
NEW
194
      await flushPromises();
×
195

NEW
196
      expect(JSZip).toHaveBeenCalledTimes(1);
×
NEW
197
      expect(mockFile).toHaveBeenCalledWith('case.tsv', 'case-content');
×
NEW
198
      expect(mockFile).toHaveBeenCalledWith('sample.tsv', 'sample-content');
×
199

NEW
200
      const readmeCall = mockFile.mock.calls.find(
×
NEW
201
        args => args[0] === 'README.txt'
×
202
      );
NEW
203
      expect(readmeCall).toBeTruthy();
×
204

NEW
205
      const readmeContent = readmeCall[1];
×
NEW
206
      expect(readmeContent).toContain(
×
207
        'The following TSV templates were downloaded from Data Dictionary Vizualizations on 2024-01-02 03:04:05 PM.'
208
      );
NEW
209
      expect(readmeContent).toContain('from "Project" node to "Sample"');
×
NEW
210
      expect(readmeContent).toContain('using data dictionary version 2024.01');
×
NEW
211
      expect(readmeContent).toContain('1. Project,Case,Sample');
×
NEW
212
      expect(readmeContent).toContain('2. Project,Study');
×
213

NEW
214
      expect(mockGenerateAsync).toHaveBeenCalledWith({ type: 'blob' });
×
NEW
215
      expect(FileSaver.saveAs).toHaveBeenCalledWith(
×
216
        'blob-content',
217
        'templates-tsv.zip'
218
      );
219
    });
220
  });
221

NEW
222
  describe('parseDictionaryNodes', () => {
×
NEW
223
    it('filters out internal keys and mismatched ids, and keeps those with category and id', () => {
×
NEW
224
      const dict = {
×
225
        case: { id: 'case', category: 'Clinical', extra: 1 },
226
        _settings: { id: '_settings', category: 'Meta' },
227
        sample: { id: 'SAMPLE', category: 'Biospecimen' },
228
        study: { id: 'study' },
229
        file: { id: 'file', category: 'Data' },
230
      };
231

NEW
232
      expect(parseDictionaryNodes(dict)).toEqual([
×
233
        { id: 'case', category: 'Clinical', extra: 1 },
234
        { id: 'file', category: 'Data' },
235
      ]);
236
    });
237
  });
238

NEW
239
  describe('getPropertyDescription', () => {
×
NEW
240
    it('returns description when only description is present', () => {
×
NEW
241
      expect(getPropertyDescription({ description: 'foo' })).toBe('foo');
×
242
    });
243

NEW
244
    it('prefers term.description when both are present', () => {
×
NEW
245
      expect(
×
246
        getPropertyDescription({
247
          description: 'foo',
248
          term: { description: 'bar' },
249
        })
250
      ).toBe('bar');
251
    });
252

NEW
253
    it('returns undefined when neither description nor term is provided', () => {
×
NEW
254
      expect(getPropertyDescription({})).toBeUndefined();
×
255
    });
256
  });
257

NEW
258
  describe('search history with localStorage', () => {
×
NEW
259
    const key = 'datadictionary:searchHistory';
×
260

NEW
261
    it('getSearchHistoryItems returns parsed items from localStorage', () => {
×
NEW
262
      localStorage.setItem(
×
263
        key,
264
        JSON.stringify([{ keywordStr: 'k1', matchedCount: 1 }])
265
      );
266

NEW
267
      expect(getSearchHistoryItems()).toEqual([
×
268
        { keywordStr: 'k1', matchedCount: 1 },
269
      ]);
270
    });
271

NEW
272
    it('addSearchHistoryItems returns existing items when keywordStr is empty', () => {
×
NEW
273
      localStorage.setItem(
×
274
        key,
275
        JSON.stringify([{ keywordStr: 'old', matchedCount: 2 }])
276
      );
277

NEW
278
      const res = addSearchHistoryItems({ keywordStr: '', matchedCount: 0 });
×
279

NEW
280
      expect(res).toEqual([{ keywordStr: 'old', matchedCount: 2 }]);
×
NEW
281
      expect(JSON.parse(localStorage.getItem(key))).toEqual([
×
282
        { keywordStr: 'old', matchedCount: 2 },
283
      ]);
284
    });
285

NEW
286
    it('addSearchHistoryItems adds new item to the beginning when not present', () => {
×
NEW
287
      localStorage.setItem(key, JSON.stringify([]));
×
288

NEW
289
      const res = addSearchHistoryItems({ keywordStr: 'new', matchedCount: 5 });
×
290

NEW
291
      expect(res).toEqual([{ keywordStr: 'new', matchedCount: 5 }]);
×
NEW
292
      expect(JSON.parse(localStorage.getItem(key))).toEqual([
×
293
        { keywordStr: 'new', matchedCount: 5 },
294
      ]);
295
    });
296

NEW
297
    it('addSearchHistoryItems moves existing item to the beginning and updates it', () => {
×
NEW
298
      localStorage.setItem(
×
299
        key,
300
        JSON.stringify([
301
          { keywordStr: 'k1', matchedCount: 1 },
302
          { keywordStr: 'k2', matchedCount: 2 },
303
        ])
304
      );
305

NEW
306
      const res = addSearchHistoryItems({ keywordStr: 'k2', matchedCount: 99 });
×
307

NEW
308
      expect(res).toEqual([
×
309
        { keywordStr: 'k2', matchedCount: 99 },
310
        { keywordStr: 'k1', matchedCount: 1 },
311
      ]);
NEW
312
      expect(JSON.parse(localStorage.getItem(key))).toEqual(res);
×
313
    });
314

NEW
315
    it('clearSearchHistoryItems clears items and returns an empty array', () => {
×
NEW
316
      localStorage.setItem(
×
317
        key,
318
        JSON.stringify([{ keywordStr: 'x', matchedCount: 1 }])
319
      );
320

NEW
321
      const res = clearSearchHistoryItems();
×
322

NEW
323
      expect(res).toEqual([]);
×
NEW
324
      expect(JSON.parse(localStorage.getItem(key))).toEqual([]);
×
325
    });
326
  });
327
});
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