diff --git a/.changeset/little-toys-reflect.md b/.changeset/little-toys-reflect.md new file mode 100644 index 00000000000..eb81c39634f --- /dev/null +++ b/.changeset/little-toys-reflect.md @@ -0,0 +1,5 @@ +--- +'@growi/core': patch +--- + +Fix generateChildrenRegExp method diff --git a/apps/app/src/server/service/page/index.ts b/apps/app/src/server/service/page/index.ts index fd639462538..e11cc43306d 100644 --- a/apps/app/src/server/service/page/index.ts +++ b/apps/app/src/server/service/page/index.ts @@ -4391,7 +4391,7 @@ class PageService implements IPageService { const Page = mongoose.model('Page') as unknown as PageModel; const ancestorPaths = isTopPage(path) ? ['/'] : collectAncestorPaths(path); // root path is necessary for rendering - const regexps = ancestorPaths.map(path => new RegExp(generateChildrenRegExp(path))); // cannot use re2 + const regexps = ancestorPaths.map(path => generateChildrenRegExp(path)); // cannot use re2 // get pages at once const queryBuilder = new PageQueryBuilder(Page.find({ path: { $in: regexps } }), true); diff --git a/packages/core/src/utils/page-path-utils/generate-children-regexp.spec.ts b/packages/core/src/utils/page-path-utils/generate-children-regexp.spec.ts new file mode 100644 index 00000000000..c696127bd3d --- /dev/null +++ b/packages/core/src/utils/page-path-utils/generate-children-regexp.spec.ts @@ -0,0 +1,55 @@ +import { describe, expect, test } from 'vitest'; + +import { generateChildrenRegExp } from './generate-children-regexp'; + +describe('generateChildrenRegExp', () => { + describe.each([ + { + path: '/', + expected: '^\\/[^/]+$', + validPaths: ['/child', '/test'], + invalidPaths: ['/', '/child/grandchild'], + }, + { + path: '/parent', + expected: '^\\/parent(\\/[^/]+)\\/?$', + validPaths: ['/parent/child', '/parent/test'], + invalidPaths: ['/parent', '/parent/child/grandchild', '/other/path'], + }, + { + path: '/parent (with brackets)', + expected: '^\\/parent \\(with brackets\\)(\\/[^/]+)\\/?$', + validPaths: ['/parent (with brackets)/child', '/parent (with brackets)/test'], + invalidPaths: ['/parent (with brackets)', '/parent (with brackets)/child/grandchild'], + }, + { + path: '/parent[with square]', + expected: '^\\/parent\\[with square\\](\\/[^/]+)\\/?$', + validPaths: ['/parent[with square]/child', '/parent[with square]/test'], + invalidPaths: ['/parent[with square]', '/parent[with square]/child/grandchild'], + }, + { + path: '/parent*with+special?chars', + expected: '^\\/parent\\*with\\+special\\?chars(\\/[^/]+)\\/?$', + validPaths: ['/parent*with+special?chars/child', '/parent*with+special?chars/test'], + invalidPaths: ['/parent*with+special?chars', '/parent*with+special?chars/child/grandchild'], + }, + ])('with path: $path', ({ + path, expected, validPaths, invalidPaths, + }) => { + test('should generate correct regexp pattern', () => { + const result = generateChildrenRegExp(path); + expect(result.source).toBe(expected); + }); + + test.each(validPaths)('should match valid path: %s', (validPath) => { + const result = generateChildrenRegExp(path); + expect(validPath).toMatch(result); + }); + + test.each(invalidPaths)('should not match invalid path: %s', (invalidPath) => { + const result = generateChildrenRegExp(path); + expect(invalidPath).not.toMatch(result); + }); + }); +}); diff --git a/packages/core/src/utils/page-path-utils/generate-children-regexp.ts b/packages/core/src/utils/page-path-utils/generate-children-regexp.ts new file mode 100644 index 00000000000..37d75dc54cc --- /dev/null +++ b/packages/core/src/utils/page-path-utils/generate-children-regexp.ts @@ -0,0 +1,16 @@ +import escapeStringRegexp from 'escape-string-regexp'; + +import { isTopPage } from './is-top-page'; + +/** + * Generate RegExp instance for one level lower path + */ +export const generateChildrenRegExp = (path: string): RegExp => { + // https://regex101.com/r/laJGzj/1 + // ex. /any_level1 + if (isTopPage(path)) return new RegExp(/^\/[^/]+$/); + + // https://regex101.com/r/mrDJrx/1 + // ex. /parent/any_child OR /any_level1 + return new RegExp(`^${escapeStringRegexp(path)}(\\/[^/]+)\\/?$`); +}; diff --git a/packages/core/src/utils/page-path-utils/index.ts b/packages/core/src/utils/page-path-utils/index.ts index 59af24a2255..94f9de93292 100644 --- a/packages/core/src/utils/page-path-utils/index.ts +++ b/packages/core/src/utils/page-path-utils/index.ts @@ -8,6 +8,7 @@ import { addTrailingSlash } from '../path-utils'; import { isTopPage as _isTopPage } from './is-top-page'; export const isTopPage = _isTopPage; +export * from './generate-children-regexp'; /** * Whether path is the top page of users @@ -276,19 +277,6 @@ export const hasSlash = (str: string): boolean => { return str.includes('/'); }; -/** - * Generate RegExp instance for one level lower path - */ -export const generateChildrenRegExp = (path: string): RegExp => { - // https://regex101.com/r/laJGzj/1 - // ex. /any_level1 - if (isTopPage(path)) return new RegExp(/^\/[^/]+$/); - - // https://regex101.com/r/mrDJrx/1 - // ex. /parent/any_child OR /any_level1 - return new RegExp(`^${path}(\\/[^/]+)\\/?$`); -}; - /** * Get username from user page path * @param path string