From c06734d5b44463813181d023cc34de09c8fb47a9 Mon Sep 17 00:00:00 2001 From: Marcin Kasprowicz Date: Mon, 21 May 2018 14:44:25 +0200 Subject: [PATCH] Cover utils with unit tests --- apps/xprof_gui/priv/package.json | 2 +- .../priv/src/store/configureStore.js | 34 +- .../priv/src/utils/ActionUtils.spec.js | 393 ++++++++++++++++++ apps/xprof_gui/priv/src/utils/ActionsUtils.js | 10 +- apps/xprof_gui/priv/src/utils/CommonUtils.js | 28 -- .../priv/src/utils/CommonUtils.spec.js | 236 +++++++++++ 6 files changed, 652 insertions(+), 51 deletions(-) create mode 100644 apps/xprof_gui/priv/src/utils/ActionUtils.spec.js create mode 100644 apps/xprof_gui/priv/src/utils/CommonUtils.spec.js diff --git a/apps/xprof_gui/priv/package.json b/apps/xprof_gui/priv/package.json index e672fe7e..89126e68 100644 --- a/apps/xprof_gui/priv/package.json +++ b/apps/xprof_gui/priv/package.json @@ -89,7 +89,7 @@ "start": "node scripts/start.js", "start:with-cowboy": "node scripts/start-with-cowboy.js", "build": "node scripts/build.js", - "test": "node scripts/test.js --env=jsdom", + "test": "node --inspect scripts/test.js --env=jsdom", "test:single-run": "node scripts/test.js --env=jsdom --coverage" }, "lint-staged": { diff --git a/apps/xprof_gui/priv/src/store/configureStore.js b/apps/xprof_gui/priv/src/store/configureStore.js index 2c5a2ed5..2b5348f9 100644 --- a/apps/xprof_gui/priv/src/store/configureStore.js +++ b/apps/xprof_gui/priv/src/store/configureStore.js @@ -4,28 +4,28 @@ import rootReducer from '../reducers'; export default function configureStore(initialState) { // Uncomment for logging - const logger = store => next => (action) => { - if ( - action.type && - action.type !== 'UPDATE_DATA' && - action.type !== 'UPDATE_MONITORED_FUNCTIONS' && - action.type !== 'UPDATE_CALLS' - ) { - console.group(action.type); - console.info('dispatching', action); - console.log('next state', store.getState()); - console.groupEnd(action.type); - } - const result = next(action); - return result; - }; + // const logger = store => next => (action) => { + // if ( + // action.type && + // action.type !== 'UPDATE_DATA' && + // action.type !== 'UPDATE_MONITORED_FUNCTIONS' && + // action.type !== 'UPDATE_CALLS' + // ) { + // console.group(action.type); + // console.info('dispatching', action); + // console.log('next state', store.getState()); + // console.groupEnd(action.type); + // } + // const result = next(action); + // return result; + // }; const store = createStore( rootReducer, initialState, - // applyMiddleware(ReduxThunk), + applyMiddleware(ReduxThunk), // Uncomment for logging - applyMiddleware(logger, ReduxThunk), + // applyMiddleware(logger, ReduxThunk), ); return store; diff --git a/apps/xprof_gui/priv/src/utils/ActionUtils.spec.js b/apps/xprof_gui/priv/src/utils/ActionUtils.spec.js new file mode 100644 index 00000000..16d0fc45 --- /dev/null +++ b/apps/xprof_gui/priv/src/utils/ActionUtils.spec.js @@ -0,0 +1,393 @@ +import * as ActionUtils from './ActionsUtils'; +import * as XProf from '../api'; +import { CALLS_COLUMNS, SORT, DPS_LIMIT } from '../constants'; + +jest.mock('../actions'); +jest.mock('../api'); + +const dispatch = jest.fn(); + +describe('Action utils', () => { + describe('determineNextCallsForFun', () => { + const name = 'ets:lookup/2'; + const items = [ + { + id: 1, pid: '<0.380.0>', call_time: 8, args: 'arg', res: '2', + }, + { + id: 2, pid: '<0.379.0>', call_time: 4, args: 'arg', res: '2', + }, + { + id: 3, pid: '<0.380.0>', call_time: 14, args: 'arg', res: '2', + }, + ]; + let calls; + + beforeEach(() => { + calls = { + [name]: [ + { + capture_id: 1, + items, + has_more: false, + sort: { + items: items.map(item => ({ ...item, expanded: false })), + column: CALLS_COLUMNS.ID, + order: SORT.ASCENDING, + }, + }, + ], + }; + }); + + it('should create new array when app initialize', () => { + // given + const json = { capture_id: 1, has_more: false, items }; + // when + const result = + ActionUtils.determineNextCallsForFun(json, undefined, undefined, name); + // then + expect(result.length).toBe(1); + expect(result[0].capture_id).toBe(json.capture_id); + expect(result[0].has_more).toBe(json.has_more); + expect(result[0].items).toBe(json.items); + expect(result[0].sort.items[0].expanded).toBe(false); + }); + + it('should create new array for first functions calls list', () => { + // given + const json = { capture_id: 2, has_more: true, items }; + // when + const result = + ActionUtils.determineNextCallsForFun(json, undefined, undefined, name); + // then + expect(result.length).toBe(1); + expect(result[0].capture_id).toBe(json.capture_id); + expect(result[0].has_more).toBe(json.has_more); + expect(result[0].items).toBe(json.items); + expect(result[0].sort.items[0].expanded).toBe(false); + }); + + it('should add new element in array for new set of calls', () => { + // given + const json = { capture_id: 2, has_more: true, items }; + // when + const result = + ActionUtils.determineNextCallsForFun(json, calls[name][0], calls, name); + // then + expect(result.length).toBe(2); + expect(result[1].capture_id).toBe(json.capture_id); + expect(result[1].has_more).toBe(json.has_more); + expect(result[1].items).toBe(json.items); + expect(result[1].sort.items[0].expanded).toBe(false); + }); + + it('should add incoming items to array', () => { + // given + const json = { capture_id: 1, has_more: true, items }; + // when + const result = + ActionUtils.determineNextCallsForFun(json, calls[name][0], calls, name); + // then + expect(result.length).toBe(1); + expect(result[0].items.length) + .toBe(json.items.length + calls[name][0].items.length); + expect(result[0].has_more).toBe(json.has_more); + expect(result[0].items).toEqual([...calls[name][0].items, ...json.items]); + expect(result[0].sort.items[0].expanded).toBe(false); + }); + + it('should expand sort incoming items and add to array', () => { + // given + const json = { capture_id: 1, has_more: true, items }; + calls[name][0].sort.column = CALLS_COLUMNS.CALL_TIME; + calls[name][0].sort.order = SORT.DESCENDING; + // when + const result = + ActionUtils.determineNextCallsForFun(json, calls[name][0], calls, name); + // then + expect(result.length).toBe(1); + expect(result[0].items.length) + .toBe(json.items.length + calls[name][0].items.length); + expect(result[0].has_more).toBe(json.has_more); + expect(result[0].items).toEqual([...calls[name][0].items, ...json.items]); + expect(result[0].sort.items.length).toBe(result[0].items.length); + expect(result[0].sort.items) + .not.toEqual([...calls[name][0].items, ...json.items]); + expect(result[0].sort.items[0].expanded).toBe(false); + }); + + it('should add incoming items to last object in result array', () => { + // given + const json = { capture_id: 1, has_more: false, items }; + calls[name].push({}); + calls[name].push({}); + // when + const result = + ActionUtils.determineNextCallsForFun(json, calls[name][0], calls, name); + // then + expect(result.length).toBe(3); + }); + }); + + describe('determineNextControl', () => { + const items = [ + { + id: 1, pid: '<0.380.0>', call_time: 8, args: 'arg', res: '2', + }, + { + id: 2, pid: '<0.379.0>', call_time: 4, args: 'arg', res: '2', + }, + { + id: 3, pid: '<0.380.0>', call_time: 14, args: 'arg', res: '2', + }, + ]; + const lastCalls = { + capture_id: 1, + items, + has_more: false, + sort: { + items, + column: 'id', + order: 'ASCENDING', + }, + }; + + it('should reset calls control during app initialization', () => { + // given + const json = { capture_id: 1, has_more: false, items }; + // when + const result = ActionUtils.determineNextControl(json, undefined); + // then + expect(result.threshold).toBeUndefined(); + expect(result.limit).toBeUndefined(); + expect(result.collecting).toBe(false); + }); + + it('should reset calls control during capturing last calls', () => { + // given + const json = { capture_id: 1, has_more: false, items }; + // when + const result = ActionUtils.determineNextControl(json, lastCalls); + // then + expect(result.threshold).toBeUndefined(); + expect(result.limit).toBeUndefined(); + expect(result.collecting).toBe(false); + }); + + it('should set calls control during capturing', () => { + // given + const json = { + capture_id: 1, has_more: true, items, threshold: '10', limit: '20', + }; + // when + const result = ActionUtils.determineNextControl(json, undefined); + // then + expect(result.threshold).toBe(json.threshold); + expect(result.limit).toBe(json.limit); + expect(result.collecting).toBe(true); + }); + + it('should set calls control during capturing of next calls', () => { + // given + const json = { + capture_id: 2, has_more: true, items, threshold: '10', limit: '20', + }; + // when + const result = ActionUtils.determineNextControl(json, lastCalls); + // then + expect(result.threshold).toBe(json.threshold); + expect(result.limit).toBe(json.limit); + expect(result.collecting).toBe(true); + }); + }); + + describe('determineIncomingDps', () => { + it('should fill with zeros', () => { + // given + const dps = [{ time: 1000 }, { time: 1001 }, { time: 1002 }]; + const timestamp = 0; + // when + const result = ActionUtils.determineIncomingDps(dps, timestamp); + // then + expect(result.length).toBe(DPS_LIMIT + dps.length); + expect(result[0].time).toBe((dps[0].time - DPS_LIMIT) * 1000); + expect(result[result.length - 1].time) + .toBe(dps[dps.length - 1].time * 1000); + }); + + it('should fill gap with zeros', () => { + // given + const dps = [{ time: 1000 }, { time: 1001 }, { time: 1002 }]; + const timestamp = 990; + // when + const result = ActionUtils.determineIncomingDps(dps, timestamp); + // then + expect(result.length).toBe(dps[dps.length - 1].time - timestamp); + expect(result[0].time).toBe((timestamp + 1) * 1000); + expect(result[result.length - 1].time) + .toBe(dps[dps.length - 1].time * 1000); + }); + + it('should return new dps', () => { + // given + const dps = [{ time: 1000 }, { time: 1001 }, { time: 1002 }]; + const timestamp = 999; + // when + const result = ActionUtils.determineIncomingDps(dps, timestamp); + // then + expect(result.length).toBe(3); + expect(result).toEqual(dps.map(sample => ({ + ...sample, + time: sample.time * 1000, + }))); + }); + + it('should return empty result if cant make decision', () => { + // given + const dps = [{ time: 1000 }, { time: 1001 }, { time: 1002 }]; + const timestamp = 11111; + // when + const result = ActionUtils.determineIncomingDps(dps, timestamp); + // then + expect(result.length).toBe(0); + }); + }); + + + describe('determineNextCalls', () => { + const mfasSingle = [['mod', 'fun', 'arity', 'full']]; + const mfasTwo = [ + ...mfasSingle, + ['mod2', 'fun2', 'arity2', 'full2'], + ]; + + beforeEach(() => { + XProf.getFunctionsCalls.mockClear(); + XProf.getFunctionsCalls.mockReturnValue({ json: [] }); + }); + + it("should call xprof api for function's calls", async () => { + // given + const state = { tracing: { calls: {} } }; + const { calls } = state.tracing; + // when + await ActionUtils.determineNextCalls(dispatch, state, mfasTwo, calls); + // then + expect(XProf.getFunctionsCalls).toHaveBeenCalledTimes(2); + }); + + it('should set calls control', async () => { + // given + const state = { tracing: { calls: {} } }; + const { calls } = state.tracing; + + XProf.getFunctionsCalls.mockReturnValue({ + json: { + capture_id: 1, + items: [1, 2], + }, + }); + // when + await ActionUtils.determineNextCalls(dispatch, state, mfasSingle, calls); + // then + expect(dispatch).toHaveBeenCalledTimes(1); + }); + + it('should return properly shaped object', async () => { + // given + const state = { tracing: { calls: {} } }; + const { calls } = state.tracing; + + XProf.getFunctionsCalls.mockReturnValue({ + json: { + capture_id: 1, + items: [1, 2], + }, + }); + // when + const result = await ActionUtils + .determineNextCalls(dispatch, state, mfasTwo, calls); + // then + expect(result[mfasTwo[0][3]]).toBeDefined(); + expect(result[mfasTwo[1][3]]).toBeDefined(); + }); + }); + + describe('determineNextData', () => { + const name1 = 'full'; + const name2 = 'full2'; + + const mfasSingle = [['mod', 'fun', 'arity', name1]]; + const mfasTwo = [ + ...mfasSingle, + ['mod2', 'fun2', 'arity2', name2], + ]; + + beforeEach(() => { + XProf.getFunctionsSamples.mockClear(); + XProf.getFunctionsSamples.mockReturnValue({ json: [] }); + }); + + it('should return next set of data', async () => { + // given + XProf.getFunctionsSamples.mockReturnValue({ json: [{ time: 13 }] }); + const data = { + [name1]: [{ time: 10000 }, { time: 11000 }, { time: 12000 }], + }; + // when + const result = await ActionUtils.determineNextData(mfasSingle, data); + // then + expect(result[name1].length).toBe(4); + }); + + it('should return array with lenght of DPS_LIMIT', async () => { + // given + XProf.getFunctionsSamples.mockReturnValue({ json: [{ time: 13 }] }); + // when + const result = await ActionUtils.determineNextData(mfasSingle, {}); + // then + expect(result[name1].length).toBe(DPS_LIMIT); + }); + + it('should call XProf API for data for each function', async () => { + // given + XProf.getFunctionsSamples.mockReturnValue({ json: [{ time: 13 }] }); + // when + await ActionUtils.determineNextData(mfasTwo, {}); + // then + expect(XProf.getFunctionsSamples).toHaveBeenCalledTimes(2); + }); + }); + + describe('determineNextControlSwitch', () => { + const name = 'module.fun/arity'; + const mfa = ['module', 'fun', 'arity', name]; + + beforeEach(() => { + XProf.stopCapturingFunctionsCalls.mockClear(); + XProf.stopCapturingFunctionsCalls.mockReturnValue({ json: [] }); + + XProf.startCapturingFunctionsCalls.mockClear(); + XProf.startCapturingFunctionsCalls.mockReturnValue({ json: [] }); + }); + + it('should stop capturing functions calls', async () => { + // when + const result = await ActionUtils + .determineNextControlSwitch({ collecting: true }, mfa); + // then + expect(XProf.stopCapturingFunctionsCalls).toHaveBeenCalledTimes(1); + expect(result.collecting).toBe(false); + }); + + it('should start capturing functions calls', async () => { + // when + const result = await ActionUtils + .determineNextControlSwitch({ collecting: false }, mfa); + // then + expect(XProf.startCapturingFunctionsCalls).toHaveBeenCalledTimes(1); + expect(result.collecting).toBe(true); + }); + }); +}); diff --git a/apps/xprof_gui/priv/src/utils/ActionsUtils.js b/apps/xprof_gui/priv/src/utils/ActionsUtils.js index d0f583ca..97216d00 100644 --- a/apps/xprof_gui/priv/src/utils/ActionsUtils.js +++ b/apps/xprof_gui/priv/src/utils/ActionsUtils.js @@ -11,7 +11,7 @@ import { import { setCallsControl } from '../actions'; import * as XProf from '../api'; -const determineNextCallsForFun = (json, lastCalls, calls, name) => { +export const determineNextCallsForFun = (json, lastCalls, calls, name) => { let callsForFun; switch (callsDecision(json, lastCalls)) { @@ -55,7 +55,7 @@ const determineNextCallsForFun = (json, lastCalls, calls, name) => { has_more: json.has_more, sort: { items: - lastCalls.sort.column === CALLS_COLUMNS.PID && + lastCalls.sort.column === CALLS_COLUMNS.ID && lastCalls.sort.order === SORT.ASCENDING ? [ ...lastCalls.sort.items, @@ -81,7 +81,7 @@ const determineNextCallsForFun = (json, lastCalls, calls, name) => { return callsForFun; }; -const determineNextControl = (json, lastcalls) => { +export const determineNextControl = (json, lastcalls) => { let control; switch (callsDecision(json, lastcalls)) { case CAPTURE_CALLS_ACTION.APP_INITIALIZATION: @@ -106,9 +106,9 @@ const determineNextControl = (json, lastcalls) => { return control; }; -const determineIncomingDps = (dps, ts) => { +export const determineIncomingDps = (dps, ts) => { let missingDps; - let mergedDps; + let mergedDps = []; const zeros = { min: 0, mean: 0, diff --git a/apps/xprof_gui/priv/src/utils/CommonUtils.js b/apps/xprof_gui/priv/src/utils/CommonUtils.js index 0bd35d79..7819329f 100644 --- a/apps/xprof_gui/priv/src/utils/CommonUtils.js +++ b/apps/xprof_gui/priv/src/utils/CommonUtils.js @@ -17,34 +17,6 @@ export const commonArrayPrefix = (sortedArray) => { return commonPrefix(string1, string2); }; -export const parseToMfa = fullName => [ - fullName.split(':')[0], - fullName.split('/')[0].split(':')[1], - +fullName.split('/')[1], - fullName, -]; - -export const getLanguageGuides = (mode) => { - if (!mode) { - return { - language: null, - type: null, - example: null, - }; - } else if (mode === 'elixir') { - return { - language: 'Elixir', - type: 'query', - example: 'Enum.member?(_, :test)', - }; - } - return { - language: 'Erlang', - type: 'trace pattern', - example: 'ets:lookup(data, _)', - }; -}; - export const callsDecision = (json, lastCalls) => { const lastCaptureId = lastCalls ? lastCalls.capture_id : undefined; let decision; diff --git a/apps/xprof_gui/priv/src/utils/CommonUtils.spec.js b/apps/xprof_gui/priv/src/utils/CommonUtils.spec.js new file mode 100644 index 00000000..1977626f --- /dev/null +++ b/apps/xprof_gui/priv/src/utils/CommonUtils.spec.js @@ -0,0 +1,236 @@ +import * as CommonUtils from './CommonUtils'; +import { + CAPTURE_CALLS_ACTION, + DPS_ACTION, + CALLS_COLUMNS, + SORT, +} from '../constants'; + +describe('Common utils', () => { + describe('commonArrayPrefix', () => { + it('should return common prefix', () => { + // given + const functions = ['qwerty', 'asdfg', 'qweasd']; + // when + const result = CommonUtils.commonArrayPrefix(functions); + // then + expect(result).toBe('qwe'); + }); + + it('should return itself if only one element in array', () => { + // given + const functions = ['qwerty']; + // when + const result = CommonUtils.commonArrayPrefix(functions); + // then + expect(result).toBe(functions[0]); + }); + + it('should return empty string if not maching', () => { + // given + const functions = ['qwerty', 'asdfg', 'zxcvb']; + // when + const result = CommonUtils.commonArrayPrefix(functions); + // then + expect(result).toBe(''); + }); + }); + + describe('callsDecision', () => { + it('should detect app initialization based on incoming list', () => { + // given + const json = { + capture_id: 4, + items: [1, 2, 3], + has_more: false, + }; + // when + const decision = CommonUtils.callsDecision(json, undefined); + // then + expect(decision).toBe(CAPTURE_CALLS_ACTION.APP_INITIALIZATION); + }); + + it("should detect beginning of first call's capturing", () => { + // given + const json = { + capture_id: 1, + items: [1], + has_more: true, + }; + // when + const decision = CommonUtils.callsDecision(json, undefined); + // then + expect(decision).toBe(CAPTURE_CALLS_ACTION.START_FIRST_CALLS_CAPTURE); + }); + + it("should detect beginning of next call's capturing", () => { + // given + const json = { + capture_id: 2, + items: [1, 2], + has_more: true, + }; + const lastCalls = { + capture_id: 1, + }; + // when + const decision = CommonUtils.callsDecision(json, lastCalls); + // then + expect(decision).toBe(CAPTURE_CALLS_ACTION.START_NEXT_CALLS_CAPTURE); + }); + + it("should detect ongoing capturing of function's calls", () => { + // given + const json = { + capture_id: 1, + items: [1, 2], + has_more: true, + }; + const lastCalls = { + capture_id: json.capture_id, + }; + // when + const decision = CommonUtils.callsDecision(json, lastCalls); + // then + expect(decision).toBe(CAPTURE_CALLS_ACTION.CAPTURING); + }); + + it("should detect last bunch of function's calls", () => { + // given + const json = { + capture_id: 1, + items: [1, 3], + has_more: false, + }; + const lastCalls = { + capture_id: json.capture_id, + }; + // when + const decision = CommonUtils.callsDecision(json, lastCalls); + // then + expect(decision).toBe(CAPTURE_CALLS_ACTION.LAST_CALLS_CAPTURE); + }); + + it('should return undefined decision', () => { + // given + const json = { + capture_id: undefined, + }; + // when + const decision = CommonUtils.callsDecision(json, undefined); + // then + expect(decision).toBe(undefined); + }); + }); + + describe('dpsDecision', () => { + it('should detect first samples', () => { + // given + const timestamp = 0; + // when + const decision = CommonUtils.dpsDecision([], timestamp); + // then + expect(decision).toBe(DPS_ACTION.FIRST_DPS); + }); + + it('should detect gap in time beetwen last sample and new samples', () => { + // given + const timestamp = 10; + const dps = [{ time: 15 }, { time: 16 }]; + // when + const decision = CommonUtils.dpsDecision(dps, timestamp); + // then + expect(decision).toBe(DPS_ACTION.MISSING_DPS); + }); + + it('should detect continuous samples', () => { + // given + const timestamp = 10; + const dps = [{ time: 11 }, { time: 12 }]; + // when + const decision = CommonUtils.dpsDecision(dps, timestamp); + // then + expect(decision).toBe(DPS_ACTION.CONTINUOUS_DPS); + }); + + it('should return undefined decision', () => { + // given + const timestamp = 10; + const dps = [{ time: 5 }, { time: 6 }]; + // when + const decision = CommonUtils.dpsDecision(dps, timestamp); + // then + expect(decision).toBe(undefined); + }); + }); + + describe('isIntegerInRange', () => { + it('should return true if integer is in range', () => { + expect(CommonUtils.isIntegerInRange(15, 10, 20)).toBe(true); + }); + it('should return false if integer is lower than range', () => { + expect(CommonUtils.isIntegerInRange(5, 10, 20)).toBe(false); + }); + it('should return true if integer equals lower limit', () => { + expect(CommonUtils.isIntegerInRange(10, 10, 20)).toBe(true); + }); + it('should return true if integer equals upper limit', () => { + expect(CommonUtils.isIntegerInRange(20, 10, 20)).toBe(true); + }); + it('should return false if integer is higher than range', () => { + expect(CommonUtils.isIntegerInRange(25, 10, 20)).toBe(false); + }); + it('should return true if string integer is in range', () => { + expect(CommonUtils.isIntegerInRange('15', 10, 20)).toBe(true); + }); + it('shoud return false if value is float', () => { + expect(CommonUtils.isIntegerInRange(15.234, 10, 20)).toBe(false); + }); + }); + + describe('sortItems', () => { + it('should sort asceding by id number values', () => { + // given + const toSort = [{ id: 1 }, { id: 2 }]; + // when + const sorted = CommonUtils + .sortItems(toSort, CALLS_COLUMNS.ID, SORT.ASCENDING); + // then + expect(sorted[0].id).toBe(toSort[0].id); + expect(sorted[1].id).toBe(toSort[1].id); + }); + + it('should sort asceding by id number values', () => { + // given + const toSort = [{ id: 1 }, { id: 2 }]; + // when + const sorted = CommonUtils + .sortItems(toSort, CALLS_COLUMNS.ID, SORT.DESCENDING); + // then + expect(sorted[0].id).toBe(toSort[1].id); + expect(sorted[1].id).toBe(toSort[0].id); + }); + + it('should sort asceding by id string values', () => { + // given + const toSort = [{ id: 'abc' }, { id: 'cba' }]; + // when + const sorted = CommonUtils + .sortItems(toSort, CALLS_COLUMNS.ID, SORT.DESCENDING); + // then + expect(sorted[0].id).toBe(toSort[1].id); + expect(sorted[1].id).toBe(toSort[0].id); + }); + + it('should sort asceding by id string values', () => { + // given + const toSort = [{ id: 'abc' }, { id: 'cba' }]; + // when + const sorted = CommonUtils + .sortItems(toSort, CALLS_COLUMNS.ID, SORT.DESCENDING); + // then + expect(sorted[0].id).toBe(toSort[1].id); + expect(sorted[1].id).toBe(toSort[0].id); + }); + }); +});