diff --git a/lib/domain/dtos/filters/LhcFillsFilterDto.js b/lib/domain/dtos/filters/LhcFillsFilterDto.js index c42dadf229..d1d2af5929 100644 --- a/lib/domain/dtos/filters/LhcFillsFilterDto.js +++ b/lib/domain/dtos/filters/LhcFillsFilterDto.js @@ -11,7 +11,11 @@ * or submit itself to any jurisdiction. */ const Joi = require('joi'); +const { validateRange } = require('../../../utilities/rangeUtils'); exports.LhcFillsFilterDto = Joi.object({ hasStableBeams: Joi.boolean(), + fillNumbers: Joi.string().trim().custom(validateRange).messages({ + 'any.invalid': '{{#message}}', + }), }); diff --git a/lib/domain/dtos/filters/RunFilterDto.js b/lib/domain/dtos/filters/RunFilterDto.js index 0c8c032b63..0feda0ddbc 100644 --- a/lib/domain/dtos/filters/RunFilterDto.js +++ b/lib/domain/dtos/filters/RunFilterDto.js @@ -18,6 +18,7 @@ const { IntegerComparisonDto, FloatComparisonDto } = require('./NumericalCompari const { RUN_CALIBRATION_STATUS } = require('../../enums/RunCalibrationStatus.js'); const { RUN_DEFINITIONS } = require('../../enums/RunDefinition.js'); const { singleRunsCollectionCustomCheck } = require('../utils.js'); +const { validateRange } = require('../../../utilities/rangeUtils.js'); const DetectorsFilterDto = Joi.object({ operator: Joi.string().valid('or', 'and', 'none').required(), @@ -30,35 +31,6 @@ const EorReasonFilterDto = Joi.object({ description: Joi.string(), }); -/** - * Validates run numbers ranges to not exceed 100 runs - * - * @param {*} value The value to validate - * @param {*} helpers The helpers object - * @returns {Object} The value if validation passes - */ -const validateRange = (value, helpers) => { - const MAX_RANGE_SIZE = 100; - - const runNumbers = value.split(',').map((runNumber) => runNumber.trim()); - - for (const runNumber of runNumbers) { - if (runNumber.includes('-')) { - const [start, end] = runNumber.split('-').map((n) => parseInt(n, 10)); - if (Number.isNaN(start) || Number.isNaN(end) || start > end) { - return helpers.error('any.invalid', { message: `Invalid range: ${runNumber}` }); - } - const rangeSize = end - start + 1; - - if (rangeSize > MAX_RANGE_SIZE) { - return helpers.error('any.invalid', { message: `Given range exceeds max size of ${MAX_RANGE_SIZE} runs: ${runNumber}` }); - } - } - } - - return value; -}; - exports.RunFilterDto = Joi.object({ runNumbers: Joi.string().trim().custom(validateRange).messages({ 'any.invalid': '{{#message}}', diff --git a/lib/public/components/Filters/LhcFillsFilter/fillNumberFilter.js b/lib/public/components/Filters/LhcFillsFilter/fillNumberFilter.js new file mode 100644 index 0000000000..de13af7586 --- /dev/null +++ b/lib/public/components/Filters/LhcFillsFilter/fillNumberFilter.js @@ -0,0 +1,25 @@ +/** + * @license + * Copyright CERN and copyright holders of ALICE Trg. This software is + * distributed under the terms of the GNU General Public License v3 (GPL + * Version 3), copied verbatim in the file "COPYING". + * + * See http://alice-Trg.web.cern.ch/license for full licensing information. + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +import { rawTextFilter } from '../common/filters/rawTextFilter.js'; + +/** + * Component to filter LHC-fills by fill number + * + * @param {RawTextFilterModel} filterModel the filter model + * @returns {Component} the text field + */ +export const fillNumberFilter = (filterModel) => rawTextFilter( + filterModel, + { classes: ['w-100', 'fill-numbers-filter'], placeholder: 'e.g. 11392, 11383, 7625' }, +); diff --git a/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js b/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js index f575652b34..5442ed8bcc 100644 --- a/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js +++ b/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js @@ -24,6 +24,7 @@ import { infologgerLinksComponents } from '../../../components/common/externalLi import { formatBeamType } from '../../../utilities/formatting/formatBeamType.js'; import { frontLink } from '../../../components/common/navigation/frontLink.js'; import { toggleStableBeamOnlyFilter } from '../../../components/Filters/LhcFillsFilter/stableBeamFilter.js'; +import { fillNumberFilter } from '../../../components/Filters/LhcFillsFilter/fillNumberFilter.js'; /** * List of active columns for a lhc fills table @@ -49,6 +50,7 @@ export const lhcFillsActiveColumns = { ), ], ), + filter: (lhcFillModel) => fillNumberFilter(lhcFillModel.filteringModel.get('fillNumbers')), profiles: { lhcFill: true, environment: true, diff --git a/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js b/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js index 787d467fe5..55a417dc66 100644 --- a/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js +++ b/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js @@ -14,6 +14,7 @@ import { buildUrl } from '/js/src/index.js'; import { FilteringModel } from '../../../components/Filters/common/FilteringModel.js'; import { StableBeamFilterModel } from '../../../components/Filters/LhcFillsFilter/StableBeamFilterModel.js'; +import { RawTextFilterModel } from '../../../components/Filters/common/filters/RawTextFilterModel.js'; import { OverviewPageModel } from '../../../models/OverviewModel.js'; import { addStatisticsToLhcFill } from '../../../services/lhcFill/addStatisticsToLhcFill.js'; @@ -32,6 +33,7 @@ export class LhcFillsOverviewModel extends OverviewPageModel { super(); this._filteringModel = new FilteringModel({ + fillNumbers: new RawTextFilterModel(), hasStableBeams: new StableBeamFilterModel(), }); diff --git a/lib/usecases/lhcFill/GetAllLhcFillsUseCase.js b/lib/usecases/lhcFill/GetAllLhcFillsUseCase.js index dd5cc41f2c..360b396968 100644 --- a/lib/usecases/lhcFill/GetAllLhcFillsUseCase.js +++ b/lib/usecases/lhcFill/GetAllLhcFillsUseCase.js @@ -22,6 +22,8 @@ const { const { lhcFillAdapter } = require('../../database/adapters/index.js'); const { ApiConfig } = require('../../config/index.js'); const { RunDefinition } = require('../../domain/enums/RunDefinition.js'); +const { unpackNumberRange } = require('../../utilities/rangeUtils.js'); +const { splitStringToStringsTrimmed } = require('../../utilities/stringUtils.js'); /** * GetAllLhcFillsUseCase @@ -38,14 +40,28 @@ class GetAllLhcFillsUseCase { const { filter, page = {} } = query; const { limit = ApiConfig.pagination.limit, offset = 0 } = page; + const SEARCH_ITEMS_SEPARATOR = ','; + const queryBuilder = new QueryBuilder(); if (filter) { - const { hasStableBeams } = filter; + const { hasStableBeams, fillNumbers } = filter; if (hasStableBeams) { // For now, if a stableBeamsStart is present, then a beam is stable queryBuilder.where('stableBeamsStart').not().is(null); } + + if (fillNumbers) { + const fillNumberCriteria = splitStringToStringsTrimmed(fillNumbers, SEARCH_ITEMS_SEPARATOR); + + const finalFillnumberList = Array.from(unpackNumberRange(fillNumberCriteria)); + + // Check that the final fill numbers list contains at least one valid fill number + if (finalFillnumberList.length > 0) { + finalFillnumberList.length === 1 ? queryBuilder.where('fillNumber').is(finalFillnumberList[0]) + : queryBuilder.where('fillNumber').oneOf(...finalFillnumberList); + } + } } const { count, rows } = await TransactionHelper.provide(async () => { diff --git a/lib/usecases/run/GetAllRunsUseCase.js b/lib/usecases/run/GetAllRunsUseCase.js index 77f9f0420b..d25762ad00 100644 --- a/lib/usecases/run/GetAllRunsUseCase.js +++ b/lib/usecases/run/GetAllRunsUseCase.js @@ -23,6 +23,8 @@ const { BadParameterError } = require('../../server/errors/BadParameterError'); const { gaqService } = require('../../server/services/qualityControlFlag/GaqService.js'); const { qcFlagSummaryService } = require('../../server/services/qualityControlFlag/QcFlagSummaryService.js'); const { DetectorType } = require('../../domain/enums/DetectorTypes.js'); +const { unpackNumberRange } = require('../../utilities/rangeUtils.js'); +const { splitStringToStringsTrimmed } = require('../../utilities/stringUtils.js'); /** * GetAllRunsUseCase @@ -83,29 +85,9 @@ class GetAllRunsUseCase { } = filter; if (runNumbers) { - const runNumberCriteria = runNumbers.split(SEARCH_ITEMS_SEPARATOR) - .map((runNumbers) => runNumbers.trim()) - .filter(Boolean); - - const runNumberSet = new Set(); - - runNumberCriteria.forEach((runNumber) => { - if (runNumber.includes('-')) { - const [start, end] = runNumber.split('-').map((n) => parseInt(n, 10)); - if (!Number.isNaN(start) && !Number.isNaN(end)) { - for (let i = start; i <= end; i++) { - runNumberSet.add(i); - } - } - } else { - const parsedRunNumber = parseInt(runNumber, 10); - if (!Number.isNaN(parsedRunNumber)) { - runNumberSet.add(parsedRunNumber); - } - } - }); + const runNumberCriteria = splitStringToStringsTrimmed(runNumbers, SEARCH_ITEMS_SEPARATOR); - const finalRunNumberList = Array.from(runNumberSet); + const finalRunNumberList = Array.from(unpackNumberRange(runNumberCriteria)); // Check that the final run numbers list contains at least one valid run number if (finalRunNumberList.length > 0) { diff --git a/lib/utilities/rangeUtils.js b/lib/utilities/rangeUtils.js new file mode 100644 index 0000000000..4cc9a385de --- /dev/null +++ b/lib/utilities/rangeUtils.js @@ -0,0 +1,79 @@ +/** + * @license + * Copyright CERN and copyright holders of ALICE O2. This software is + * distributed under the terms of the GNU General Public License v3 (GPL + * Version 3), copied verbatim in the file "COPYING". + * + * See http://alice-o2.web.cern.ch/license for full licensing information. + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +/** + * Validates numbers ranges to not exceed 100 entities + * Expects a string containing comma seperated number values. + * + * @param {string} value The value to validate + * @param {*} helpers The helpers object + * @returns {Object} The value if validation passes + */ +export const validateRange = (value, helpers) => { + const MAX_RANGE_SIZE = 100; + + const numbers = value.split(',').map((number) => number.trim()); + + for (const number of numbers) { + if (number.includes('-')) { + // Check if '-' occurs more than once in this part of the range + if (number.lastIndexOf('-') !== number.indexOf('-')) { + return helpers.error('any.invalid', { message: `Invalid range: ${number}` }); + } + const [start, end] = number.split('-').map((n) => Number(n)); + if (Number.isNaN(start) || Number.isNaN(end) || start > end) { + return helpers.error('any.invalid', { message: `Invalid range: ${number}` }); + } + const rangeSize = end - start + 1; + + if (rangeSize > MAX_RANGE_SIZE) { + return helpers.error('any.invalid', { message: `Given range exceeds max size of ${MAX_RANGE_SIZE} range: ${number}` }); + } + } else { + // Prevent non-numeric input. + if (isNaN(number)) { + return helpers.error('any.invalid', { message: `Invalid number: ${number}` }); + } + } + } + + return value; +}; + +/** + * Unpacks a given string containing number ranges. + * E.G. input: 5,7-9 => output: 5,7,8,9 + * @param {string[]} numbersRanges numbers that may or may not contain ranges. + * @param {string} rangeSplitter string used to indicate and unpack a range. + * @returns {Set} set containing the unpacked range. + */ +export function unpackNumberRange(numbersRanges, rangeSplitter = '-') { + // Set to prevent duplicate values. + const resultNumbers = new Set(); + + numbersRanges.forEach((number) => { + if (number.includes(rangeSplitter)) { + const [start, end] = number.split(rangeSplitter).map((n) => parseInt(n, 10)); + if (!Number.isNaN(start) && !Number.isNaN(end)) { + for (let i = start; i <= end; i++) { + resultNumbers.add(Number(i)); + } + } + } else { + if (!isNaN(number)) { + resultNumbers.add(Number(number)); + } + } + }); + return resultNumbers; +} diff --git a/lib/utilities/stringUtils.js b/lib/utilities/stringUtils.js index c00f860a2d..cc302cbed4 100644 --- a/lib/utilities/stringUtils.js +++ b/lib/utilities/stringUtils.js @@ -62,6 +62,16 @@ const snakeToCamel = (snake) => snake.toLowerCase() */ const snakeToPascal = (snake) => ucFirst(snakeToCamel(snake)); +/** + * Split the received string to an array of trimmed strings. + * Boolean trick: https://michaeluloth.com/javascript-filter-boolean/ + * @param {string} stringCollection String containing other strings withing split by seperator. + * @param {string} stringSeperator Used to seperate the stringCollection. + */ +const splitStringToStringsTrimmed = (stringCollection, stringSeperator = ',') => stringCollection.split(stringSeperator) + .map((string) => string.trim()) + .filter(Boolean); + exports.ucFirst = ucFirst; exports.lcFirst = lcFirst; @@ -73,3 +83,5 @@ exports.pascalToSnake = pascalToSnake; exports.snakeToCamel = snakeToCamel; exports.snakeToPascal = snakeToPascal; + +exports.splitStringToStringsTrimmed = splitStringToStringsTrimmed; diff --git a/test/api/runs.test.js b/test/api/runs.test.js index 2c96ed46f5..4322040bb3 100644 --- a/test/api/runs.test.js +++ b/test/api/runs.test.js @@ -166,7 +166,7 @@ module.exports = () => { expect(response.status).to.equal(400); const { errors: [error] } = response.body; expect(error.title).to.equal('Invalid Attribute'); - expect(error.detail).to.equal(`Given range exceeds max size of ${MAX_RANGE_SIZE} runs: ${runNumberRange}`); + expect(error.detail).to.equal(`Given range exceeds max size of ${MAX_RANGE_SIZE} range: ${runNumberRange}`); }); it('should return 400 if the calibration status filter is invalid', async () => { diff --git a/test/lib/usecases/lhcFill/GetAllLhcFillsUseCase.test.js b/test/lib/usecases/lhcFill/GetAllLhcFillsUseCase.test.js index 089420a321..fdccf49678 100644 --- a/test/lib/usecases/lhcFill/GetAllLhcFillsUseCase.test.js +++ b/test/lib/usecases/lhcFill/GetAllLhcFillsUseCase.test.js @@ -40,4 +40,64 @@ module.exports = () => { expect(lhcFill.stableBeamsStart).to.not.be.null; }); }); + + // Fill number filter tests + + it('should only contain specified fill number', async () => { + getAllLhcFillsDto.query = { filter: { hasStableBeams: true, fillNumbers: '6' } }; + const { lhcFills } = await new GetAllLhcFillsUseCase().execute(getAllLhcFillsDto); + expect(lhcFills).to.be.an('array').and.lengthOf(1) + + lhcFills.forEach((lhcFill) => { + expect(lhcFill.fillNumber).to.equal(6) + }); + }) + + it('should only contain specified fill numbers', async () => { + getAllLhcFillsDto.query = { filter: { hasStableBeams: true, fillNumbers: '6,3' } }; + const { lhcFills } = await new GetAllLhcFillsUseCase().execute(getAllLhcFillsDto); + + + expect(lhcFills).to.be.an('array').and.lengthOf(2) + + lhcFills.forEach((lhcFill) => { + expect(lhcFill.fillNumber).oneOf([6,3]) + }); + }) + + it('should only contain specified fill numbers, range', async () => { + getAllLhcFillsDto.query = { filter: { hasStableBeams: true, fillNumbers: '1-3,6' } }; + const { lhcFills } = await new GetAllLhcFillsUseCase().execute(getAllLhcFillsDto); + + + expect(lhcFills).to.be.an('array').and.lengthOf(4) + + lhcFills.forEach((lhcFill) => { + expect(lhcFill.fillNumber).oneOf([1,2,3,6]) + }); + }) + + it('should only contain specified fill numbers, whitespace', async () => { + getAllLhcFillsDto.query = { filter: { hasStableBeams: true, fillNumbers: ' 6 , 3 ' } }; + const { lhcFills } = await new GetAllLhcFillsUseCase().execute(getAllLhcFillsDto); + + + expect(lhcFills).to.be.an('array').and.lengthOf(2) + + lhcFills.forEach((lhcFill) => { + expect(lhcFill.fillNumber).oneOf([6,3]) + }); + }) + + it('should only contain specified fill numbers, comma misplacement', async () => { + getAllLhcFillsDto.query = { filter: { hasStableBeams: true, fillNumbers: ',6,3,' } }; + const { lhcFills } = await new GetAllLhcFillsUseCase().execute(getAllLhcFillsDto); + + + expect(lhcFills).to.be.an('array').and.lengthOf(2) + + lhcFills.forEach((lhcFill) => { + expect(lhcFill.fillNumber).oneOf([6,3]) + }); + }) }; diff --git a/test/lib/utilities/index.js b/test/lib/utilities/index.js index f074095e0c..cc8b2202ed 100644 --- a/test/lib/utilities/index.js +++ b/test/lib/utilities/index.js @@ -14,6 +14,7 @@ const cacheAsyncFunctionTest = require('./cacheAsyncFunction.test.js'); const deepmerge = require('./deepmerge.test.js'); const isPromise = require('./isPromise.test.js'); +const rangeUtilsTest = require('./rangeUtils.test.js'); const stringUtilsTest = require('./stringUtils.test.js'); module.exports = () => { @@ -21,4 +22,5 @@ module.exports = () => { describe('deepmerge', deepmerge); describe('isPromise', isPromise); describe('stringUtils', stringUtilsTest); + describe('rangeUtils', rangeUtilsTest) }; diff --git a/test/lib/utilities/rangeUtils.test.js b/test/lib/utilities/rangeUtils.test.js new file mode 100644 index 0000000000..509db85a4f --- /dev/null +++ b/test/lib/utilities/rangeUtils.test.js @@ -0,0 +1,159 @@ +/** + * @license + * Copyright CERN and copyright holders of ALICE O2. This software is + * distributed under the terms of the GNU General Public License v3 (GPL + * Version 3), copied verbatim in the file "COPYING". + * + * See http://alice-o2.web.cern.ch/license for full licensing information. + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +const Sinon = require('sinon'); +const { validateRange, unpackNumberRange } = require('../../../lib/utilities/rangeUtils.js'); +const { expect } = require('chai'); + +module.exports = () => { + describe('validateRange()', () => { + let helpers; + + beforeEach(() => { + helpers = { + error: Sinon.stub() + }; + }); + + it('returns the original value for a single valid number', () => { + const input = '5'; + const result = validateRange(input, helpers); + expect(result).to.equal(input); + }); + + it('returns the original value, accepts 0', () => { + const input = '0,1'; + const result = validateRange(input, helpers); + expect(result).to.equal(input); + }); + + it('returns the original value for multiple valid numbers', () => { + const input = '1, 2,3, 10 '; + const result = validateRange(input, helpers); + expect(result).to.equal(input); + }); + + it('accepts a valid range', () => { + const input = '7-9'; + const result = validateRange(input, helpers); + expect(result).to.equal(input); + }); + + it('accepts numbers and ranges together', () => { + const input = '5,7-9,12'; + const result = validateRange(input, helpers); + expect(result).to.equal(input); + }); + + it('accepts numbers and ranges overlap', () => { + const input = '1-6,2,3,4,5,6'; + const result = validateRange(input, helpers); + expect(result).to.equal(input); + }); + + it('rejects non-numeric input', () => { + const input = '5,a,7'; + validateRange(input, helpers); + expect(helpers.error.calledOnce).to.be.true; + expect(helpers.error.firstCall.args[0]).to.equal('any.invalid'); + expect(helpers.error.firstCall.args[1]).to.deep.equal({ message: 'Invalid number: a' }); + }); + + it('rejects range with non-numeric input', () => { + const input = '3-a'; + validateRange(input, helpers); + expect(helpers.error.calledOnce).to.be.true; + expect(helpers.error.firstCall.args[1]).to.deep.equal({ message: 'Invalid range: 3-a' }); + }); + + it('rejects range where Start > End', () => { + const input = '6-5'; + validateRange(input, helpers); + expect(helpers.error.calledOnce).to.be.true; + expect(helpers.error.firstCall.args[1]).to.deep.equal({ message: 'Invalid range: 6-5' }); + }); + + // Allowed, technically a valid range + it('accepts range where Start === End', () => { + const input = '5-5'; + const result = validateRange(input, helpers); + expect(result).to.equal(input); + }); + + it('rejects range containing more than one `-`', () => { + const input = '1-2-3'; + validateRange(input, helpers); + expect(helpers.error.calledOnce).to.be.true; + expect(helpers.error.firstCall.args[1]).to.deep.equal({ message: 'Invalid range: 1-2-3' }); + }); + + it('rejects range containing more than one `-`, at end', () => { + const input = '1-2-'; + validateRange(input, helpers); + expect(helpers.error.calledOnce).to.be.true; + expect(helpers.error.firstCall.args[1]).to.deep.equal({ message: 'Invalid range: 1-2-' }); + }); + + // MAX_RANGE_SIZE = 100, should this change, also change this test... + it('rejects a range that exceeds MAX_RANGE_SIZE', () => { + const input = '1-101'; + validateRange(input, helpers); + expect(helpers.error.calledOnce).to.be.true; + expect(helpers.error.firstCall.args[1]).to.deep.equal({ message: 'Given range exceeds max size of 100 range: 1-101' }); + }); + + it('handles whitespace around inputs', () => { + const input = ' 2 , 4-6 , 9 '; + const result = validateRange(input, helpers); + expect(result).to.equal(input); + }); + }); + + describe('unpackNumberRange()', () => { + it('unpacks single numbers, duplicate', () => { + const input = ['5', '10', '5']; + const result = unpackNumberRange(input); + expect(Array.from(result)).to.deep.equal([5, 10]); + }); + + it('unpacks range', () => { + const input = ['7-9']; + const result = unpackNumberRange(input); + expect(Array.from(result)).to.deep.equal([7, 8, 9]); + }); + + it('unpacks mixed numbers and ranges', () => { + const input = ['5', '7-9', '9', '3-4']; + const result = unpackNumberRange(input); + expect(Array.from(result)).to.deep.equal([5, 7, 8, 9, 3, 4]); + }); + + it('ignores any non-numeric inputs', () => { + const input = ['5', 'x', '2-3', 'a-b', '4-a']; + const result = unpackNumberRange(input); + expect(Array.from(result)).to.deep.equal([5, 2, 3]); + }); + + it('accepts/uses a range splitter', () => { + const input = ['8..10', '12']; + const result = unpackNumberRange(input, '..'); + expect(Array.from(result)).to.deep.equal([8, 9, 10, 12]); + }); + + // Also allowed right now... + it('returns empty set if nothing is given', () => { + const result = unpackNumberRange([]); + expect(result.size).to.equal(0); + }); + }); +}; diff --git a/test/lib/utilities/stringUtils.test.js b/test/lib/utilities/stringUtils.test.js index edbd95fbe9..fb06df6d4c 100644 --- a/test/lib/utilities/stringUtils.test.js +++ b/test/lib/utilities/stringUtils.test.js @@ -11,7 +11,7 @@ * or submit itself to any jurisdiction. */ -const { snakeToCamel, pascalToSnake, ucFirst, lcFirst, snakeToPascal } = require('../../../lib/utilities/stringUtils.js'); +const { snakeToCamel, pascalToSnake, ucFirst, lcFirst, snakeToPascal, splitStringToStringsTrimmed } = require('../../../lib/utilities/stringUtils.js'); const { expect } = require('chai'); module.exports = () => { @@ -61,6 +61,11 @@ module.exports = () => { expect(snakeToCamel('SNAKE')).to.equal('snake'); }); + it('should successfully split string into array of strings', () => { + expect(splitStringToStringsTrimmed('one , two, three ')).to.deep.equal(['one', 'two', 'three']); + expect(splitStringToStringsTrimmed('one . two. three ', '.')).to.deep.equal(['one', 'two', 'three']); + }); + it('should successfully convert snake_case string to PascalCase', () => { expect(snakeToPascal('this_is_snake_case')).to.equal('ThisIsSnakeCase'); expect(snakeToPascal('_this_is_snake_case')).to.equal('ThisIsSnakeCase'); diff --git a/test/public/lhcFills/overview.test.js b/test/public/lhcFills/overview.test.js index 269239f2c2..02b05c1591 100644 --- a/test/public/lhcFills/overview.test.js +++ b/test/public/lhcFills/overview.test.js @@ -267,10 +267,12 @@ module.exports = () => { it('should successfully display filter elements', async () => { const filterSBExpect = { selector: '.stableBeams-filter .w-30', value: 'Stable Beams Only' }; + const filterFillNRExpect = {selector: 'div.items-baseline:nth-child(1) > div:nth-child(1)', value: 'Fill #'} await goToPage(page, 'lhc-fill-overview'); // Open the filtering panel await openFilteringPanel(page); await expectInnerText(page, filterSBExpect.selector, filterSBExpect.value); + await expectInnerText(page, filterFillNRExpect.selector, filterFillNRExpect.value); }); it('should successfully un-apply Stable Beam filter menu', async () => {