mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
feat: find closest matching block and superblock for testing (#62089)
This commit is contained in:
committed by
GitHub
parent
bdccefef08
commit
3a41a6137a
@@ -11,7 +11,7 @@ const {
|
||||
} = require('./build-superblock');
|
||||
|
||||
const { buildCertification } = require('./build-certification');
|
||||
const { applyFilters } = require('./utils');
|
||||
const { applyFilters, closestFilters } = require('./utils');
|
||||
const {
|
||||
getContentDir,
|
||||
getLanguageConfig,
|
||||
@@ -310,8 +310,10 @@ async function parseCurriculumStructure(filters) {
|
||||
const superblockList = addBlockStructure(
|
||||
addSuperblockStructure(curriculum.superblocks)
|
||||
);
|
||||
const refinedFilters = closestFilters(filters, superblockList);
|
||||
const fullSuperblockList = applyFilters(superblockList, refinedFilters);
|
||||
return {
|
||||
fullSuperblockList: applyFilters(superblockList, filters),
|
||||
fullSuperblockList,
|
||||
certifications: curriculum.certifications
|
||||
};
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
"@babel/core": "7.23.7",
|
||||
"@babel/register": "7.23.7",
|
||||
"@types/polka": "^0.5.7",
|
||||
"@types/string-similarity": "^4.0.2",
|
||||
"@vitest/ui": "^3.2.4",
|
||||
"chai": "4.4.1",
|
||||
"glob": "8.1.0",
|
||||
|
||||
+32
-1
@@ -1,4 +1,7 @@
|
||||
const path = require('path');
|
||||
|
||||
const comparison = require('string-similarity');
|
||||
|
||||
const {
|
||||
SuperBlocks,
|
||||
generateSuperBlockList
|
||||
@@ -142,9 +145,37 @@ const applyFilters = createFilterPipeline([
|
||||
filterByChallengeId
|
||||
]);
|
||||
|
||||
function closestMatch(target, xs) {
|
||||
return comparison.findBestMatch(target.toLowerCase(), xs).bestMatch.target;
|
||||
}
|
||||
|
||||
function closestFilters(target, superblocks) {
|
||||
if (target?.superBlock) {
|
||||
const superblockNames = superblocks.map(({ name }) => name);
|
||||
return {
|
||||
...target,
|
||||
superBlock: closestMatch(target.superBlock, superblockNames)
|
||||
};
|
||||
}
|
||||
|
||||
if (target?.block) {
|
||||
const blocks = superblocks.flatMap(({ blocks }) =>
|
||||
blocks.map(({ dashedName }) => dashedName)
|
||||
);
|
||||
return {
|
||||
...target,
|
||||
block: closestMatch(target.block, blocks)
|
||||
};
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
exports.closestFilters = closestFilters;
|
||||
exports.closestMatch = closestMatch;
|
||||
exports.createSuperOrder = createSuperOrder;
|
||||
exports.filterByBlock = filterByBlock;
|
||||
exports.filterBySuperblock = filterBySuperblock;
|
||||
exports.filterByChallengeId = filterByChallengeId;
|
||||
exports.createSuperOrder = createSuperOrder;
|
||||
exports.getSuperOrder = getSuperOrder;
|
||||
exports.applyFilters = applyFilters;
|
||||
|
||||
+229
-151
@@ -4,6 +4,8 @@ import { describe, it, expect } from 'vitest';
|
||||
|
||||
import { SuperBlocks } from '../shared-dist/config/curriculum';
|
||||
import {
|
||||
closestFilters,
|
||||
closestMatch,
|
||||
createSuperOrder,
|
||||
filterByBlock,
|
||||
filterByChallengeId,
|
||||
@@ -124,157 +126,233 @@ describe('getSuperOrder', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('filterByChallengeId', () => {
|
||||
it('returns the same superblocks if no challengeId is provided', () => {
|
||||
const superblocks = [
|
||||
{
|
||||
name: 'superblock-1',
|
||||
blocks: [{ dashedName: 'block-1', challengeOrder: [{ id: '1' }] }]
|
||||
},
|
||||
{
|
||||
name: 'superblock-2',
|
||||
blocks: [{ dashedName: 'block-2', challengeOrder: [{ id: '2' }] }]
|
||||
}
|
||||
];
|
||||
expect(filterByChallengeId(superblocks)).toEqual(superblocks);
|
||||
});
|
||||
|
||||
it('ignores blocks without the specified challengeId', () => {
|
||||
const superblocks = [
|
||||
{
|
||||
name: 'superblock-1',
|
||||
blocks: [
|
||||
{ dashedName: 'block-1', challengeOrder: [{ id: '1' }] },
|
||||
{ dashedName: 'block-2', challengeOrder: [{ id: '2' }] }
|
||||
]
|
||||
}
|
||||
];
|
||||
const filtered = filterByChallengeId(superblocks, { challengeId: '2' });
|
||||
expect(filtered).toEqual([
|
||||
{
|
||||
name: 'superblock-1',
|
||||
blocks: [{ dashedName: 'block-2', challengeOrder: [{ id: '2' }] }]
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns only the specified challenge and its solution challenge', () => {
|
||||
const superblocks = [
|
||||
{
|
||||
name: 'superblock-1',
|
||||
blocks: [
|
||||
{
|
||||
dashedName: 'block-1',
|
||||
challengeOrder: [{ id: '1' }, { id: '2' }, { id: '3' }]
|
||||
},
|
||||
{ dashedName: 'block-2', challengeOrder: [{ id: '4' }] }
|
||||
]
|
||||
}
|
||||
];
|
||||
const filtered = filterByChallengeId(superblocks, { challengeId: '1' });
|
||||
expect(filtered).toEqual([
|
||||
{
|
||||
name: 'superblock-1',
|
||||
blocks: [
|
||||
{ dashedName: 'block-1', challengeOrder: [{ id: '1' }, { id: '2' }] }
|
||||
]
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns only superblocks containing the specified challenge', () => {
|
||||
const superblocks = [
|
||||
{
|
||||
name: 'superblock-1',
|
||||
blocks: [
|
||||
{ dashedName: 'block-1', challengeOrder: [{ id: '1' }] },
|
||||
{ dashedName: 'block-2', challengeOrder: [{ id: '2' }] }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'superblock-2',
|
||||
blocks: [{ dashedName: 'block-3', challengeOrder: [{ id: '3' }] }]
|
||||
}
|
||||
];
|
||||
const filtered = filterByChallengeId(superblocks, { challengeId: '2' });
|
||||
expect(filtered).toEqual([
|
||||
{
|
||||
name: 'superblock-1',
|
||||
blocks: [{ dashedName: 'block-2', challengeOrder: [{ id: '2' }] }]
|
||||
}
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('filterByBlock', () => {
|
||||
it('returns the same superblocks if no block is provided', () => {
|
||||
const superblocks = [
|
||||
{
|
||||
name: 'superblock-1',
|
||||
blocks: [{ dashedName: 'block-1' }, { dashedName: 'block-2' }]
|
||||
}
|
||||
];
|
||||
expect(filterByBlock(superblocks)).toEqual(superblocks);
|
||||
});
|
||||
|
||||
it('returns only the specified block', () => {
|
||||
const superblocks = [
|
||||
{
|
||||
name: 'superblock-1',
|
||||
blocks: [{ dashedName: 'block-1' }, { dashedName: 'block-2' }]
|
||||
}
|
||||
];
|
||||
const filtered = filterByBlock(superblocks, { block: 'block-1' });
|
||||
expect(filtered).toEqual([
|
||||
{
|
||||
name: 'superblock-1',
|
||||
blocks: [{ dashedName: 'block-1' }]
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns an empty array if no blocks match the specified block', () => {
|
||||
const superblocks = [
|
||||
{
|
||||
name: 'superblock-1',
|
||||
blocks: [{ dashedName: 'block-1' }, { dashedName: 'block-2' }]
|
||||
}
|
||||
];
|
||||
const filtered = filterByBlock(superblocks, { block: 'nonexistent-block' });
|
||||
expect(filtered).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('filterBySuperblock', () => {
|
||||
it('returns the same superblocks if no superBlock is provided', () => {
|
||||
const superblocks = [
|
||||
{
|
||||
name: 'superblock-1',
|
||||
blocks: [{ dashedName: 'block-1' }, { dashedName: 'block-2' }]
|
||||
}
|
||||
];
|
||||
expect(filterBySuperblock(superblocks)).toEqual(superblocks);
|
||||
});
|
||||
|
||||
it('returns only the specified superblock', () => {
|
||||
const superblocks = [
|
||||
{
|
||||
name: 'superblock-1',
|
||||
blocks: [{ dashedName: 'block-1' }, { dashedName: 'block-2' }]
|
||||
},
|
||||
{
|
||||
name: 'superblock-2',
|
||||
blocks: [{ dashedName: 'block-3' }]
|
||||
}
|
||||
];
|
||||
const filtered = filterBySuperblock(superblocks, {
|
||||
superBlock: 'superblock-1'
|
||||
describe('filter utils', () => {
|
||||
describe('filterByChallengeId', () => {
|
||||
it('returns the same superblocks if no challengeId is provided', () => {
|
||||
const superblocks = [
|
||||
{
|
||||
name: 'superblock-1',
|
||||
blocks: [{ dashedName: 'block-1', challengeOrder: [{ id: '1' }] }]
|
||||
},
|
||||
{
|
||||
name: 'superblock-2',
|
||||
blocks: [{ dashedName: 'block-2', challengeOrder: [{ id: '2' }] }]
|
||||
}
|
||||
];
|
||||
expect(filterByChallengeId(superblocks)).toEqual(superblocks);
|
||||
});
|
||||
|
||||
it('ignores blocks without the specified challengeId', () => {
|
||||
const superblocks = [
|
||||
{
|
||||
name: 'superblock-1',
|
||||
blocks: [
|
||||
{ dashedName: 'block-1', challengeOrder: [{ id: '1' }] },
|
||||
{ dashedName: 'block-2', challengeOrder: [{ id: '2' }] }
|
||||
]
|
||||
}
|
||||
];
|
||||
const filtered = filterByChallengeId(superblocks, { challengeId: '2' });
|
||||
expect(filtered).toEqual([
|
||||
{
|
||||
name: 'superblock-1',
|
||||
blocks: [{ dashedName: 'block-2', challengeOrder: [{ id: '2' }] }]
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns only the specified challenge and its solution challenge', () => {
|
||||
const superblocks = [
|
||||
{
|
||||
name: 'superblock-1',
|
||||
blocks: [
|
||||
{
|
||||
dashedName: 'block-1',
|
||||
challengeOrder: [{ id: '1' }, { id: '2' }, { id: '3' }]
|
||||
},
|
||||
{ dashedName: 'block-2', challengeOrder: [{ id: '4' }] }
|
||||
]
|
||||
}
|
||||
];
|
||||
const filtered = filterByChallengeId(superblocks, { challengeId: '1' });
|
||||
expect(filtered).toEqual([
|
||||
{
|
||||
name: 'superblock-1',
|
||||
blocks: [
|
||||
{
|
||||
dashedName: 'block-1',
|
||||
challengeOrder: [{ id: '1' }, { id: '2' }]
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns only superblocks containing the specified challenge', () => {
|
||||
const superblocks = [
|
||||
{
|
||||
name: 'superblock-1',
|
||||
blocks: [
|
||||
{ dashedName: 'block-1', challengeOrder: [{ id: '1' }] },
|
||||
{ dashedName: 'block-2', challengeOrder: [{ id: '2' }] }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'superblock-2',
|
||||
blocks: [{ dashedName: 'block-3', challengeOrder: [{ id: '3' }] }]
|
||||
}
|
||||
];
|
||||
const filtered = filterByChallengeId(superblocks, { challengeId: '2' });
|
||||
expect(filtered).toEqual([
|
||||
{
|
||||
name: 'superblock-1',
|
||||
blocks: [{ dashedName: 'block-2', challengeOrder: [{ id: '2' }] }]
|
||||
}
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('filterByBlock', () => {
|
||||
it('returns the same superblocks if no block is provided', () => {
|
||||
const superblocks = [
|
||||
{
|
||||
name: 'superblock-1',
|
||||
blocks: [{ dashedName: 'block-1' }, { dashedName: 'block-2' }]
|
||||
}
|
||||
];
|
||||
expect(filterByBlock(superblocks)).toEqual(superblocks);
|
||||
});
|
||||
|
||||
it('returns only the specified block', () => {
|
||||
const superblocks = [
|
||||
{
|
||||
name: 'superblock-1',
|
||||
blocks: [{ dashedName: 'block-1' }, { dashedName: 'block-2' }]
|
||||
}
|
||||
];
|
||||
const filtered = filterByBlock(superblocks, { block: 'block-1' });
|
||||
expect(filtered).toEqual([
|
||||
{
|
||||
name: 'superblock-1',
|
||||
blocks: [{ dashedName: 'block-1' }]
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns an empty array if no blocks match the specified block', () => {
|
||||
const superblocks = [
|
||||
{
|
||||
name: 'superblock-1',
|
||||
blocks: [{ dashedName: 'block-1' }, { dashedName: 'block-2' }]
|
||||
}
|
||||
];
|
||||
const filtered = filterByBlock(superblocks, {
|
||||
block: 'nonexistent-block'
|
||||
});
|
||||
expect(filtered).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('filterBySuperblock', () => {
|
||||
it('returns the same superblocks if no superBlock is provided', () => {
|
||||
const superblocks = [
|
||||
{
|
||||
name: 'superblock-1',
|
||||
blocks: [{ dashedName: 'block-1' }, { dashedName: 'block-2' }]
|
||||
}
|
||||
];
|
||||
expect(filterBySuperblock(superblocks)).toEqual(superblocks);
|
||||
});
|
||||
|
||||
it('returns only the specified superblock', () => {
|
||||
const superblocks = [
|
||||
{
|
||||
name: 'superblock-1',
|
||||
blocks: [{ dashedName: 'block-1' }, { dashedName: 'block-2' }]
|
||||
},
|
||||
{
|
||||
name: 'superblock-2',
|
||||
blocks: [{ dashedName: 'block-3' }]
|
||||
}
|
||||
];
|
||||
const filtered = filterBySuperblock(superblocks, {
|
||||
superBlock: 'superblock-1'
|
||||
});
|
||||
expect(filtered).toEqual([
|
||||
{
|
||||
name: 'superblock-1',
|
||||
blocks: [{ dashedName: 'block-1' }, { dashedName: 'block-2' }]
|
||||
}
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('closestMatch', () => {
|
||||
it('returns the closest matching element', () => {
|
||||
const items = [
|
||||
'responsive-web-design',
|
||||
'javascript-algorithms-and-data-structures',
|
||||
'front-end-development-libraries',
|
||||
'data-visualization'
|
||||
];
|
||||
const input = 'responsiv web design';
|
||||
const closest = 'responsive-web-design';
|
||||
expect(closestMatch(input, items)).toBe(closest);
|
||||
});
|
||||
|
||||
it('ignores case when finding the closest match', () => {
|
||||
const items = [
|
||||
'responsive-web-design',
|
||||
'ReSPonSivE-WeB-DeSiGne',
|
||||
'javascript-algorithms-and-data-structures',
|
||||
'front-end-development-libraries',
|
||||
'data-visualization'
|
||||
];
|
||||
const input = 'ReSPonSiv WeB DeSiGn';
|
||||
const closest = 'responsive-web-design';
|
||||
expect(closestMatch(input, items)).toBe(closest);
|
||||
});
|
||||
});
|
||||
|
||||
describe('closestFilters', () => {
|
||||
it('returns the closest matching superblock filter', () => {
|
||||
const superblocks = [
|
||||
{
|
||||
name: 'responsive-web-design',
|
||||
blocks: [
|
||||
{ dashedName: 'basic-html-and-html5' },
|
||||
{ dashedName: 'css-flexbox' }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'javascript-algorithms-and-data-structures',
|
||||
blocks: [{ dashedName: 'basic-javascript' }, { dashedName: 'es6' }]
|
||||
}
|
||||
];
|
||||
|
||||
expect(
|
||||
closestFilters({ superBlock: 'responsiv web design' }, superblocks)
|
||||
).toEqual({ superBlock: 'responsive-web-design' });
|
||||
});
|
||||
|
||||
it('returns the closest matching block filter', () => {
|
||||
const superblocks = [
|
||||
{
|
||||
name: 'responsive-web-design',
|
||||
blocks: [
|
||||
{ dashedName: 'basic-html-and-html5' },
|
||||
{ dashedName: 'css-flexbox' }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'javascript-algorithms-and-data-structures',
|
||||
blocks: [{ dashedName: 'basic-javascript' }, { dashedName: 'es6' }]
|
||||
}
|
||||
];
|
||||
|
||||
expect(closestFilters({ block: 'basic-javascr' }, superblocks)).toEqual({
|
||||
block: 'basic-javascript'
|
||||
});
|
||||
});
|
||||
expect(filtered).toEqual([
|
||||
{
|
||||
name: 'superblock-1',
|
||||
blocks: [{ dashedName: 'block-1' }, { dashedName: 'block-2' }]
|
||||
}
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
Generated
+8
@@ -724,6 +724,9 @@ importers:
|
||||
'@types/polka':
|
||||
specifier: ^0.5.7
|
||||
version: 0.5.7
|
||||
'@types/string-similarity':
|
||||
specifier: ^4.0.2
|
||||
version: 4.0.2
|
||||
'@vitest/ui':
|
||||
specifier: ^3.2.4
|
||||
version: 3.2.4(vitest@3.2.4)
|
||||
@@ -4823,6 +4826,9 @@ packages:
|
||||
'@types/store@2.0.5':
|
||||
resolution: {integrity: sha512-5NmTKe3GWdOaykzq7no+Ahf6mafJu0oLc9JNhJ3E26+0oFvd6GnksnZQpMXcH526mfG4xDYjFiKzyDL51PzeWQ==}
|
||||
|
||||
'@types/string-similarity@4.0.2':
|
||||
resolution: {integrity: sha512-LkJQ/jsXtCVMK+sKYAmX/8zEq+/46f1PTQw7YtmQwb74jemS1SlNLmARM2Zml9DgdDTWKAtc5L13WorpHPDjDA==}
|
||||
|
||||
'@types/superagent@4.1.19':
|
||||
resolution: {integrity: sha512-McM1mlc7PBZpCaw0fw/36uFqo0YeA6m8JqoyE4OfqXsZCIg0hPP2xdE6FM7r6fdprDZHlJwDpydUj1R++93hCA==}
|
||||
|
||||
@@ -19850,6 +19856,8 @@ snapshots:
|
||||
|
||||
'@types/store@2.0.5': {}
|
||||
|
||||
'@types/string-similarity@4.0.2': {}
|
||||
|
||||
'@types/superagent@4.1.19':
|
||||
dependencies:
|
||||
'@types/cookiejar': 2.1.2
|
||||
|
||||
Reference in New Issue
Block a user