diff --git a/test/sorting.test.js b/test/sorting.test.js index 64946e5..97483fd 100644 --- a/test/sorting.test.js +++ b/test/sorting.test.js @@ -402,6 +402,29 @@ describe('openapi-format CLI sorting tests', () => { fromEntriesSpy.mockRestore(); }); + it('sortPathsByAlphabet - should order a path before its trailing-slash variant regardless of input order', () => { + // A path and its trailing-slash variant differ only by an empty + // segment. The comparator must return the same result no matter which + // order they appear in the input, otherwise they swap between runs. + const expected = ['/pets', '/pets/']; + + const sortedA = sortPathsByAlphabet({'/pets/': {get: {}}, '/pets': {get: {}}}); + const sortedB = sortPathsByAlphabet({'/pets': {get: {}}, '/pets/': {get: {}}}); + + expect(Object.keys(sortedA)).toEqual(expected); + expect(Object.keys(sortedB)).toEqual(expected); + }); + + it('sortPathsByAlphabet - should keep a shorter path before its trailing-slash variant and deeper children', () => { + const sorted = sortPathsByAlphabet({ + '/pets/{id}': {get: {}}, + '/pets/': {get: {}}, + '/pets': {get: {}} + }); + + expect(Object.keys(sorted)).toEqual(['/pets', '/pets/', '/pets/{id}']); + }); + it('pathToRegExp and matchPath - should support named params in strict matching', () => { const regex = pathToRegExp('/messages/:id'); expect(regex).toBeInstanceOf(RegExp); diff --git a/utils/sorting.js b/utils/sorting.js index cd9b9d2..704dda8 100644 --- a/utils/sorting.js +++ b/utils/sorting.js @@ -99,8 +99,15 @@ function sortPathsByAlphabet(paths) { const pathB = b[0].split('/'); for (let i = 1; i < Math.max(pathA.length, pathB.length); i++) { - if (!pathA[i]) return -1; - if (!pathB[i]) return 1; + // Use a strict `undefined` check so a missing segment (the path has + // ended) is treated differently from an empty segment produced by a + // trailing slash. A truthy `!segment` test conflates the two, which + // makes the comparator inconsistent for paths that differ only by a + // trailing slash (e.g. `/pets` vs `/pets/`): both `compare(a, b)` and + // `compare(b, a)` would return -1, so their order depends on the input + // order and flips between runs. + if (pathA[i] === undefined) return -1; + if (pathB[i] === undefined) return 1; if (pathA[i] < pathB[i]) return -1; if (pathA[i] > pathB[i]) return 1; }