Skip to content

Commit

Permalink
refactor: moving utils to their components and url validation
Browse files Browse the repository at this point in the history
  • Loading branch information
johnvente committed Feb 15, 2024
1 parent b7b42e6 commit c963cfa
Show file tree
Hide file tree
Showing 14 changed files with 339 additions and 331 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import PropTypes from 'prop-types';
import { Button } from '@edx/paragon';
import { LinkOff } from '@edx/paragon/icons';
import { formatBlockPath } from '../utils';
import formatBlockPath from '../formatBlockPath';

import './index.scss';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import { render, fireEvent, screen } from '@testing-library/react';
import '@testing-library/jest-dom';

import { formatBlockPath } from '../utils';
import formatBlockPath from '../formatBlockPath';
import BlockLink from './index';

describe('BlockLink Component', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,8 @@ import { Button, TransitionReplace, ActionRow } from '@edx/paragon';
import { ArrowForwardIos, ArrowBack } from '@edx/paragon/icons';
import { useIntl } from '@edx/frontend-platform/i18n';

import {
blockTypes,
getSectionsList,
getChildrenFromList,
} from '../utils';
import blockTypes from '../blockTypes';
import { getSectionsList, getChildrenFromList } from './utils';

import messages from './messages';
import './index.scss';
Expand Down
34 changes: 34 additions & 0 deletions src/editors/sharedComponents/InsertLinkModal/BlocksList/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import cloneDeep from 'lodash.clonedeep';
import blockTypes from '../blockTypes';

/**
* Retrieves a list of sections from the provided blocks object.
*
* @param {Object} blocks - The blocks object containing various block types.
* @returns {Array} An array of section (type: chapter) blocks extracted from the blocks object.
*/
export const getSectionsList = (blocks = {}) => {
const blocksList = Object.keys(blocks);
return blocksList.reduce((previousBlocks, blockKey) => {
const block = cloneDeep(blocks[blockKey]);
if (block.type === blockTypes.section) {
return [...previousBlocks, block];
}

return previousBlocks;
}, []);
};

/**
* Retrieves an array of child blocks based on the children list of a selected block.
*
* @param {Object} blockSelected - The selected block for which children are to be retrieved.
* @param {Object} blocks - The blocks object containing various block types.
* @returns {Array} An array of child blocks cloned from the blocks object.
*/
export const getChildrenFromList = (blockSelected, blocks) => {
if (blockSelected.children) {
return blockSelected.children.map((key) => cloneDeep(blocks[key]));
}
return [];
};
123 changes: 123 additions & 0 deletions src/editors/sharedComponents/InsertLinkModal/BlocksList/utils.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { getSectionsList, getChildrenFromList } from './utils';

describe('BlockList utils', () => {
describe('getSectionsList function', () => {
test('returns an empty array for an empty blocks object', () => {
const result = getSectionsList({});
expect(result).toEqual([]);
});

test('returns an empty array if there are no sections in the blocks object', () => {
const blocks = {
block1: {
id: 'block1',
type: 'unit',
},
block2: {
id: 'block2',
type: 'vertical',
},
};
const result = getSectionsList(blocks);
expect(result).toEqual([]);
});

test('returns an array containing sections from the blocks object', () => {
const blocks = {
section1: {
id: 'section1',
type: 'chapter',
},
block1: {
id: 'block1',
type: 'unit',
},
section2: {
id: 'section2',
type: 'chapter',
},
block2: {
id: 'block2',
type: 'vertical',
},
};
const result = getSectionsList(blocks);
const expected = [
{
id: 'section1',
type: 'chapter',
},
{
id: 'section2',
type: 'chapter',
},
];
expect(result).toEqual(expected);
});
});

describe('getChildrenFromList function', () => {
test('returns an empty array when blockSelected has no children', () => {
const blocks = {
parentBlock: {
id: 'parentBlock',
},
};

const selectedBlock = blocks.parentBlock;
const childrenList = getChildrenFromList(selectedBlock, blocks);

expect(childrenList).toEqual([]);
});

test('returns an array of child blocks when blockSelected has children', () => {
const blocks = {
parentBlock: {
id: 'parentBlock',
children: ['child1', 'child2'],
},
child1: {
id: 'child1',
},
child2: {
id: 'child2',
},
};

const selectedBlock = blocks.parentBlock;
const childrenList = getChildrenFromList(selectedBlock, blocks);

expect(childrenList).toHaveLength(2);
expect(childrenList).toContainEqual(blocks.child1);
expect(childrenList).toContainEqual(blocks.child2);
});

test('returns an empty array when blockSelected.children is undefined', () => {
const blocks = {
parentBlock: {
id: 'parentBlock',
children: undefined,
},
};

const selectedBlock = blocks.parentBlock;
const childrenList = getChildrenFromList(selectedBlock, blocks);

expect(childrenList).toEqual([]);
});

test('returns an empty array when blockSelected.children is an empty array', () => {
const blocks = {
parentBlock: {
id: 'parentBlock',
children: [],
},
};

const selectedBlock = blocks.parentBlock;
const childrenList = getChildrenFromList(selectedBlock, blocks);

expect(childrenList).toEqual([]);
});
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Button } from '@edx/paragon';
import PropTypes from 'prop-types';

import { formatBlockPath } from '../utils';
import formatBlockPath from '../formatBlockPath';

const FilteredBlock = ({ block, onBlockFilterClick }) => {
const { title, subTitle } = formatBlockPath(block.path);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { useIntl } from '@edx/frontend-platform/i18n';
import { SearchField } from '@edx/paragon';
import FilteredBlock from '../FilteredBlock';
import { filterBlocksByText } from '../utils';
import { filterBlocksByText } from './utils';

import messages from './messages';
import './index.scss';
Expand Down
23 changes: 23 additions & 0 deletions src/editors/sharedComponents/InsertLinkModal/SearchBlocks/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* eslint-disable import/prefer-default-export */
import cloneDeep from 'lodash.clonedeep';

/**
* Filters blocks based on the provided searchText.
*
* @param {string} searchText - The text to filter blocks.
* @param {Object} blocks - The object containing blocks.
* @returns {Object} - Filtered blocks.
*/
export const filterBlocksByText = (searchText, blocks) => {
if (!searchText) {
return {};
}
const copyBlocks = cloneDeep(blocks);
return Object.keys(copyBlocks).reduce((result, key) => {
const item = copyBlocks[key];
if (item.path.toLowerCase().includes(searchText.toLowerCase())) {
result[key] = item;
}
return result;
}, {});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { filterBlocksByText } from './utils';

describe('SearchBlocks utils', () => {
describe('filterBlocksByText function', () => {
const testBlocks = {
block1: {
id: 'block1',
path: 'Root / Child 1',
},
block2: {
id: 'block2',
path: 'Root / Child 2',
},
block3: {
id: 'block3',
path: 'Another / Block',
},
};

test('returns an empty object when searchText is empty', () => {
const searchText = '';
const filteredBlocks = filterBlocksByText(searchText, testBlocks);
expect(filteredBlocks).toEqual({});
});

test('filters blocks based on case-insensitive searchText', () => {
const searchText = 'child';
const filteredBlocks = filterBlocksByText(searchText, testBlocks);
expect(filteredBlocks).toEqual({
block1: {
id: 'block1',
path: 'Root / Child 1',
},
block2: {
id: 'block2',
path: 'Root / Child 2',
},
});
});

test('returns an empty object when no blocks match searchText', () => {
const searchText = 'nonexistent';
const filteredBlocks = filterBlocksByText(searchText, testBlocks);
expect(filteredBlocks).toEqual({});
});

test('filters blocks with partial matches in path', () => {
const searchText = 'root';
const filteredBlocks = filterBlocksByText(searchText, testBlocks);
expect(filteredBlocks).toEqual({
block1: {
id: 'block1',
path: 'Root / Child 1',
},
block2: {
id: 'block2',
path: 'Root / Child 2',
},
});
});
});
});
7 changes: 7 additions & 0 deletions src/editors/sharedComponents/InsertLinkModal/blockTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const blockTypes = {
section: 'chapter',
subsection: 'sequential',
unit: 'vertical',
};

export default blockTypes;
28 changes: 28 additions & 0 deletions src/editors/sharedComponents/InsertLinkModal/formatBlockPath.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* Formats a block path into title and subtitle.
*
* @param {string} path - The path to be formatted.
* @returns {Object} - Formatted block path with title and subtitle.
*/
const formatBlockPath = (path) => {
if (!path) {
return {
title: '',
subTitle: '',
};
}
const pathSlitted = path.split(' / ');
let title = pathSlitted.pop();
const subTitle = pathSlitted.join(' / ');

if (!title.trim()) {
// If the last part is empty or contains only whitespace
title = pathSlitted.pop();
}
return {
title,
subTitle,
};
};

export default formatBlockPath;
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import formatBlockPath from './formatBlockPath';

describe('formatBlockPath function', () => {
test('formats a simple path with title and subtitle', () => {
const path = 'Root / Child 1 / Grandchild';
const formattedPath = formatBlockPath(path);
expect(formattedPath).toEqual({
title: 'Grandchild',
subTitle: 'Root / Child 1',
});
});

test('handles an empty title by using the previous part as title', () => {
const path = 'Root / Child 1 / ';
const formattedPath = formatBlockPath(path);
expect(formattedPath).toEqual({
title: 'Child 1',
subTitle: 'Root / Child 1',
});
});

test('handles an empty path by returning an empty title and subtitle', () => {
const path = '';
const formattedPath = formatBlockPath(path);
expect(formattedPath).toEqual({
title: '',
subTitle: '',
});
});

test('handles whitespace in the title by using the previous part as title', () => {
const path = 'Root / Child 1 / ';
const formattedPath = formatBlockPath(path);
expect(formattedPath).toEqual({
title: 'Child 1',
subTitle: 'Root / Child 1',
});
});

test('handles a path with only one part by using it as the title', () => {
const path = 'SinglePart';
const formattedPath = formatBlockPath(path);
expect(formattedPath).toEqual({
title: 'SinglePart',
subTitle: '',
});
});
});
Loading

0 comments on commit c963cfa

Please sign in to comment.