mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-09-01 15:02:09 +08:00
feat: reject FCC_ filters that are too imprecise (#67823)
This commit is contained in:
committed by
GitHub
parent
266b1d7235
commit
f1add0fc64
@@ -404,6 +404,9 @@ export async function parseCurriculumStructure(filter?: Filter) {
|
||||
addSuperblockStructure(curriculum.superblocks)
|
||||
);
|
||||
const refinedFilter = closestFilters(superblockList, filter);
|
||||
if (!isEmpty(filter)) {
|
||||
console.log('Applied filter:', refinedFilter);
|
||||
}
|
||||
const fullSuperblockList = applyFilters(superblockList, refinedFilter);
|
||||
return {
|
||||
fullSuperblockList,
|
||||
|
||||
+299
-243
@@ -1,4 +1,4 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { describe, it, expect, beforeAll, vi } from 'vitest';
|
||||
|
||||
import {
|
||||
closestFilters,
|
||||
@@ -8,261 +8,317 @@ import {
|
||||
filterBySuperblock
|
||||
} from './filter';
|
||||
|
||||
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);
|
||||
describe('filters', () => {
|
||||
beforeAll(() => {
|
||||
vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
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 all superblocks containing that block', () => {
|
||||
const superblocks = [
|
||||
{
|
||||
name: 'superblock-1',
|
||||
blocks: [{ dashedName: 'block-1' }, { dashedName: 'block-2' }]
|
||||
},
|
||||
{
|
||||
name: 'superblock-2',
|
||||
blocks: [{ dashedName: 'block-1' }, { dashedName: 'block-2' }]
|
||||
}
|
||||
];
|
||||
const filtered = filterByBlock(superblocks, { block: 'block-1' });
|
||||
expect(filtered).toEqual([
|
||||
{
|
||||
name: 'superblock-1',
|
||||
blocks: [{ dashedName: 'block-1' }]
|
||||
},
|
||||
{
|
||||
name: 'superblock-2',
|
||||
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'
|
||||
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);
|
||||
});
|
||||
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'
|
||||
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' }] }]
|
||||
}
|
||||
]);
|
||||
});
|
||||
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('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' }] }]
|
||||
}
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
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('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);
|
||||
});
|
||||
|
||||
describe('closestFilters', () => {
|
||||
it('returns the closest matching superblock filter', () => {
|
||||
const superblocks = [
|
||||
{
|
||||
name: 'responsive-web-design',
|
||||
blocks: [
|
||||
{ dashedName: 'basic-html-and-html5', challengeOrder: [] },
|
||||
{ dashedName: 'css-flexbox', challengeOrder: [] }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'javascript-algorithms-and-data-structures',
|
||||
blocks: [
|
||||
{ dashedName: 'basic-javascript', challengeOrder: [] },
|
||||
{ dashedName: 'es6', challengeOrder: [] }
|
||||
]
|
||||
}
|
||||
];
|
||||
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' }]
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
expect(
|
||||
closestFilters(superblocks, { superBlock: 'responsiv web design' })
|
||||
).toEqual({ superBlock: 'responsive-web-design' });
|
||||
it('returns all superblocks containing that block', () => {
|
||||
const superblocks = [
|
||||
{
|
||||
name: 'superblock-1',
|
||||
blocks: [{ dashedName: 'block-1' }, { dashedName: 'block-2' }]
|
||||
},
|
||||
{
|
||||
name: 'superblock-2',
|
||||
blocks: [{ dashedName: 'block-1' }, { dashedName: 'block-2' }]
|
||||
}
|
||||
];
|
||||
const filtered = filterByBlock(superblocks, { block: 'block-1' });
|
||||
expect(filtered).toEqual([
|
||||
{
|
||||
name: 'superblock-1',
|
||||
blocks: [{ dashedName: 'block-1' }]
|
||||
},
|
||||
{
|
||||
name: 'superblock-2',
|
||||
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([]);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns the closest matching block filter', () => {
|
||||
const superblocks = [
|
||||
{
|
||||
name: 'responsive-web-design',
|
||||
blocks: [
|
||||
{ dashedName: 'basic-html-and-html5', challengeOrder: [] },
|
||||
{ dashedName: 'css-flexbox', challengeOrder: [] }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'javascript-algorithms-and-data-structures',
|
||||
blocks: [
|
||||
{ dashedName: 'basic-javascript', challengeOrder: [] },
|
||||
{ dashedName: 'es6', challengeOrder: [] }
|
||||
]
|
||||
}
|
||||
];
|
||||
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);
|
||||
});
|
||||
|
||||
expect(closestFilters(superblocks, { block: 'basic-javascr' })).toEqual({
|
||||
block: 'basic-javascript'
|
||||
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).closest).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).closest).toBe(closest);
|
||||
});
|
||||
});
|
||||
|
||||
describe('closestFilters', () => {
|
||||
it('returns the closest matching superblock filter', () => {
|
||||
const superblocks = [
|
||||
{
|
||||
name: 'responsive-web-design',
|
||||
blocks: [
|
||||
{ dashedName: 'basic-html-and-html5', challengeOrder: [] },
|
||||
{ dashedName: 'css-flexbox', challengeOrder: [] }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'javascript-algorithms-and-data-structures',
|
||||
blocks: [
|
||||
{ dashedName: 'basic-javascript', challengeOrder: [] },
|
||||
{ dashedName: 'es6', challengeOrder: [] }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
expect(
|
||||
closestFilters(superblocks, { superBlock: 'responsiv web design' })
|
||||
).toEqual({ superBlock: 'responsive-web-design' });
|
||||
});
|
||||
|
||||
it('returns the closest matching block filter', () => {
|
||||
const superblocks = [
|
||||
{
|
||||
name: 'responsive-web-design',
|
||||
blocks: [
|
||||
{ dashedName: 'basic-html-and-html5', challengeOrder: [] },
|
||||
{ dashedName: 'css-flexbox', challengeOrder: [] }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'javascript-algorithms-and-data-structures',
|
||||
blocks: [
|
||||
{ dashedName: 'basic-javascript', challengeOrder: [] },
|
||||
{ dashedName: 'es6', challengeOrder: [] }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
expect(closestFilters(superblocks, { block: 'basic-javascr' })).toEqual({
|
||||
block: 'basic-javascript'
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw if the closest match has Dice-Sørensen score below the threshold', () => {
|
||||
const superblocks = [
|
||||
{
|
||||
name: 'responsive-web-design',
|
||||
blocks: [
|
||||
{ dashedName: 'basic-html-and-html5', challengeOrder: [] },
|
||||
{ dashedName: 'css-flexbox', challengeOrder: [] }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'javascript-algorithms-and-data-structures',
|
||||
blocks: [
|
||||
{ dashedName: 'basic-javascript', challengeOrder: [] },
|
||||
{ dashedName: 'es6', challengeOrder: [] }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
expect(() =>
|
||||
closestFilters(superblocks, { block: 'unrelated-block-name' })
|
||||
).toThrow(
|
||||
'No close match found for block: unrelated-block-name. Found "basic-html-and-html5", is that what you meant?'
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw if the closest match (for superblocks) has Dice-Sørensen score below the threshold', () => {
|
||||
const superblocks = [
|
||||
{
|
||||
name: 'responsive-web-design',
|
||||
blocks: [
|
||||
{ dashedName: 'basic-html-and-html5', challengeOrder: [] },
|
||||
{ dashedName: 'css-flexbox', challengeOrder: [] }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'javascript-algorithms-and-data-structures',
|
||||
blocks: [
|
||||
{ dashedName: 'basic-javascript', challengeOrder: [] },
|
||||
{ dashedName: 'es6', challengeOrder: [] }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
expect(() =>
|
||||
closestFilters(superblocks, { superBlock: 'unrelated-block-name' })
|
||||
).toThrow(
|
||||
'No close match found for superBlock: unrelated-block-name. Found "javascript-algorithms-and-data-structures", is that what you meant?'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -163,11 +163,14 @@ function getSimilarityScore(a: string, b: string): number {
|
||||
return (2 * intersection) / (a.length + b.length - 2);
|
||||
}
|
||||
|
||||
export function closestMatch(target: string, xs: string[]): string {
|
||||
export function closestMatch(
|
||||
target: string,
|
||||
xs: string[]
|
||||
): { closest: string; score: number } {
|
||||
const [firstCandidate, ...rest] = xs;
|
||||
|
||||
if (!firstCandidate) {
|
||||
return target;
|
||||
return { closest: target, score: 0 };
|
||||
}
|
||||
|
||||
const normalizedTarget = normalizeForComparison(target);
|
||||
@@ -190,13 +193,16 @@ export function closestMatch(target: string, xs: string[]): string {
|
||||
}
|
||||
}
|
||||
|
||||
return closest;
|
||||
return { closest, score: closestScore };
|
||||
}
|
||||
|
||||
export function closestFilters(
|
||||
superblocks: Filterable[],
|
||||
target?: Filter
|
||||
): Filter | undefined {
|
||||
// This is subjective, but should allow through typos while rejecting overly vague or unrelated filters.
|
||||
const diceSorensenThreshold = 0.7;
|
||||
|
||||
if (target?.superBlock) {
|
||||
const superblockNames = superblocks.map(({ name }) => name);
|
||||
|
||||
@@ -205,9 +211,17 @@ export function closestFilters(
|
||||
return target;
|
||||
}
|
||||
|
||||
const { closest, score } = closestMatch(target.superBlock, superblockNames);
|
||||
|
||||
if (score < diceSorensenThreshold) {
|
||||
throw Error(
|
||||
`No close match found for superBlock: ${target.superBlock}. Found "${closest}", is that what you meant?`
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
...target,
|
||||
superBlock: closestMatch(target.superBlock, superblockNames)
|
||||
superBlock: closest
|
||||
};
|
||||
}
|
||||
|
||||
@@ -215,9 +229,18 @@ export function closestFilters(
|
||||
const blocks = superblocks.flatMap(({ blocks }) =>
|
||||
blocks.map(({ dashedName }) => dashedName)
|
||||
);
|
||||
|
||||
const { closest, score } = closestMatch(target.block, blocks);
|
||||
|
||||
if (score < diceSorensenThreshold) {
|
||||
throw Error(
|
||||
`No close match found for block: ${target.block}. Found "${closest}", is that what you meant?`
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
...target,
|
||||
block: closestMatch(target.block, blocks)
|
||||
block: closest
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user